您的位置:首页 > 科技 > IT业 > Linux线程--线程创建、等待及退出

Linux线程--线程创建、等待及退出

2024/12/26 21:24:06 来源:https://blog.csdn.net/qq_61391875/article/details/139375678  浏览:    关键词:Linux线程--线程创建、等待及退出

1. pthread_create

功能

创建一个新的线程。

原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数
  • thread: 指向pthread_t类型变量的指针,用于存储新创建线程的标识符。
  • attr: 线程属性,通常为NULL表示默认属性。如果需要指定线程属性,可以使用pthread_attr_t结构。
  • start_routine: 新线程的入口函数,即新线程将从这个函数开始执行。线程开始执行的函数类型为void *(*start_routine)(void *),它接受一个void *参数并返回一个void *值。
  • arg: 传递给新线程入口函数的参数。
返回值
  • 成功:返回0。
  • 失败:返回错误代码。
pthread_t t1;
int param = 100;
int ret = pthread_create(&t1, NULL, func1, (void *)&param);
if (ret != 0) {printf("Error creating thread: %d\n", ret);
}

2. pthread_self

功能:获取调用线程的线程标识符。
原型
pthread_t pthread_self(void);
参数:无。
返回值:返回调用线程的线程标识符。

用途: 常用于调试、日志记录以及需要区分线程的情景。

pthread_t tid = pthread_self();
printf("Current thread ID: %ld\n", (unsigned long)tid);

3. pthread_join

功能:等待指定的线程终止。pthread_join会阻塞调用它的线程,直到指定的线程结束。这对于确保所有线程在主线程退出之前完成工作非常有用。
原型
int pthread_join(pthread_t thread, void **retval);
参数
  • thread: 要等待的线程的标识符。
  • retval: 存储线程返回值的指针。如果不需要获取线程的返回值,可以传递NULL。如果需要获取线程的返回值,retval参数不能为NULL,应该传递一个指向指针的指针。
返回值
  • 成功:返回0。
  • 失败:返回错误代码。
示例
pthread_t t1;
// 创建线程 t1
pthread_create(&t1, NULL, func1, (void *)&param);// 等待线程 t1 结束
pthread_join(t1, NULL);

4. pthread_exit

功能:终止调用
它的线程。该函数用于显式地终止线程,通常用于线程需要提前结束的情况。

自动清理: 调用pthread_exit的线程会自动释放分配给它的资源。

原型
void pthread_exit(void *retval);
参数
  • retval: 线程的返回值,传递给pthread_joinretval参数。
返回值

无,因为此函数不会返回到调用线程。

示例
void *func1(void *arg) {printf("Thread is exiting.\n");pthread_exit(NULL);
}

示例代码

结合上述函数,这里做个演示

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void * func1(void *arg)
{//关于为什么要使用static,请看下面的解释static char *p = "t1 is run"; //为什么转换成unsigned long,请看下面的解释printf("func1: %ld\n", (unsigned long)pthread_self());//退出当前线程,并返回一个指针给pthread_joinpthread_exit((void *)p);
}int main()
{//定义一个线程标识符pthread_t t1;int data = 100;char *pret = NULL;int ret = pthread_create(&t1, NULL, func1, (void *)&data);if(ret == 0){printf("successfully creat a pthread!\n");}else{printf("faild to creat a pthread\n");}printf("I am main: %ld\n", (unsigned long)pthread_self());pthread_join(t1, (void **)&pret);printf("main: t1 quit: %s\n", pret);return 0;
}

问题1:为什么要使用static静态变量?

非静态变量的行为

考虑一个没有使用 static 的情况:

#include <stdio.h>void *func1(void *arg) {char p[] = "t1 is run";  // 非静态局部变量return (void *)p;
}int main() {void *result = func1(NULL);printf("Result: %s\n", (char *)result);  // 未定义行为,可能导致错误return 0;
}

在这个例子中:

  • char p[] 是一个局部数组变量,存储在栈中。
  • func1 返回时,p 指向的内存可能已无效,因为函数返回后栈帧被销毁。
  • 访问返回的指针会导致未定义行为,可能会崩溃或输出垃圾值。

         C 语言标准中规定,对于局部变量(自动变量),当函数返回时,它们的内存会被释放。如果你返回一个指向局部变量的指针,这个指针会指向无效内存。虽然在某些情况下,内存内容可能未被立即覆盖或销毁,因此可以“侥幸”地正确打印,但这种做法是不安全的。

使用静态变量

现在使用 static 关键字:

#include <stdio.h>void *func1(void *arg) {static char p[] = "t1 is run";  // 静态局部变量return (void *)p;
}int main() {void *result = func1(NULL);printf("Result: %s\n", (char *)result);  // 正常输出 "t1 is run"return 0;
}

在这个例子中:

  • static char p[] 是一个静态变量,存储在静态数据区而不是栈中。
  • 变量 p 在第一次调用时初始化,并在程序的整个运行期间保留其值和内存地址。
  • func1 返回时,返回的指针 p 始终有效,可以安全地访问。

关键点总结

  • 持久性:静态变量在整个程序运行期间存在,不会因函数返回而销毁。
  • 唯一初始化:静态变量只在第一次声明时初始化,后续调用不会重新初始化。
  • 作用域限制:静态局部变量的作用域限制在函数内部,无法被外部访问。

使用静态变量的场景

静态变量适用于以下场景:

  1. 返回局部数据的地址:当函数需要返回局部数据的地址时,可以使用静态变量确保数据在函数返回后依然有效。
  2. 保持函数调用间的状态:当函数需要在多次调用之间保持某些状态时,使用静态变量可以实现状态的持久化。

 问题2:为什么将pthread_self()的返回值转换为unsigned long类型?

  1. 不透明类型pthread_t 是一个不透明类型,其实现依赖于具体的操作系统和线程库。在某些系统上,pthread_t 可能是一个整数类型,在其他系统上可能是一个指针类型。
  2. 类型大小不同:不同系统上,pthread_t 的大小可能不同(例如,32位系统和64位系统上类型大小可能不同)。

使用 unsigned long 的理由

  1. 通用格式化unsigned long 是一种标准的整数类型,在C语言中占用固定的字节数(通常是32位或64位)。这使得它在大多数系统上都可以以一种一致的方式打印。
  2. 格式化支持printf 函数支持 %lu 格式说明符来打印 unsigned long 类型,这在C语言标准库中是通用的。
  3. 避免类型问题:将 pthread_t 转换为 unsigned long 可以避免由于不同系统上 pthread_t 类型不同带来的编译和运行时问题。

问题3:关于线程函数

void *thread_func(void *arg) {}pthread_create(&t1, NULL, thread_func, (void *)&param);
  • 线程函数: thread_func 是符合 void *(*)(void *) 类型的函数,它接受一个 void * 类型的参数,并返回一个 void * 类型的值。

  • 创建线程: pthread_create(&t1, NULL, thread_func, (void *)&param);

    • 第一个参数 &t1 是指向线程标识符的指针。
    • 第二个参数 NULL 表示使用默认线程属性。
    • 第三个参数 thread_func 是线程函数的指针。
    • 第四个参数 (void *)&param 是传递给线程函数的参数,强制转换为 void * 类型。为什么要强制转换呢,接着往下看问题4,就明白了。

 问题4:pthread_create的第三个参数类型void *(*start_routine)(void *),怎么去理解?

我们可以拆分来理解:

  1. void *: 表示函数的返回类型是一个void *指针。线程函数可以返回任意类型的数据,通过指针的方式返回。

  2. (*start_routine): 表示这是一个指向函数的指针,指向的函数名为start_routine

  3. (void *): 表示这个函数接受一个void *类型的参数。这个参数可以是任意类型的数据,通过指针的方式传递。

void *(*start_routine)(void *) 表示一个指向函数的指针(*start_routine),这个函数接受一个void *类型的参数(void *),并返回一个void *类型的值。也就是说对应的函数要为指针函数。

问题5:关于多次使用到void *

void * 类型

void * 是一种通用指针类型,可以指向任意类型的数据。它不携带任何类型信息,这意味着你可以将任何类型的数据指针转换为 void *,并且可以从 void * 类型转换回原始类型的指针。

为什么使用 void *

在需要传递不确定类型的数据时,void * 非常有用。例如,在多线程编程中,你可能希望线程函数能够处理不同类型的参数,而 void * 允许你传递任意类型的数据指针。

问题6:pthread_join的第二个参数

pthread_join 内部如何工作

pthread_join 的实现中,内部逻辑大概如下:

  1. 等待线程结束:主线程会阻塞等待子线程 t1 结束。
  2. 获取线程的返回值:当线程调用 pthread_exit((void *)p) 退出时,返回的指针 p 会被传递回 pthread_join
  3. 存储返回值pthread_join 将返回的指针 p 存储在 retval 所指向的地址中。

        在 pthread_join 调用中传递 &pret 的目的是让线程库在 pthread_join 内部通过这个指针来修改 pret 的值(意思是把pret的地址穿给线程库,线程库知道了pret在哪,然后才能找到pret去修改pret的值),使 pret 指向线程函数返回的地址。意思是把pret的地址穿给线程库,线程库知道了pret在哪,然后才能找到pret去修改pret的值。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com