1. 线程创建
功能: 创建一个新的线程。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数说明:
thread:返回线程 ID。attr:设置线程属性,为 NULL 表示使用默认属性。start_routine:线程启动后要执行的函数地址。arg:传给线程启动函数的参数。
返回值: 成功返回 0;失败返回错误码。
错误检查: Pthreads 函数出错时不会设置全局变量 errno,而是将错误代码通过返回值返回。建议通过返回值判定错误,因为读取返回值比读取线程内的 errno 变量开销更小。
线程 ID 及进程地址空间布局: pthread_create 函数产生的线程 ID 存放在第一个参数指向的地址中。该 ID 属于 NPTL 线程库范畴,用于后续操作线程。NPTL 提供了 pthread_self 函数获取线程自身 ID:
pthread_t pthread_self(void);
2. 线程等待
新线程的创建后,主进程需要等待,否则会出现类似僵尸进程的问题。
pthread_join 接口:
- 返回值:成功为 0,失败返回错误码。
- 第一个参数:指定要等待的线程 ID。
- 第二个参数:二级指针,若不需要获取返回值可设为 nullptr。
- 行为:默认阻塞等待。
获取线程返回值: 线程退出时的返回值类型为 void*,存储在系统资源中。用户需定义一个 void* 变量,将其地址传入 pthread_join 的第二个参数。当线程退出时,会将返回值传给该指针指向的内存区域。
异常处理: 线程一旦异常,整个进程会挂掉,其他线程也会受影响。因此 pthread_join 不考虑线程异常,只需主进程考虑进程层面的异常即可。
3. 线程终止 (退出)
在进程中可使用 return 和 exit 退出,但在线程中直接调用 exit 会导致整个进程挂掉。线程可通过以下方式终止:
return:从线程函数返回。pthread_exit(void *retval):显式终止当前线程并传递返回值。pthread_cancel(pthread_t tid):主进程可关闭某个已存在的线程,此时等待返回值变为 PTHREAD_CANCELED。
4. 原生线程库
Linux 中的 pthread 库是原生线程库。C++11 虽然支持多线程,但在 g++ 编译时仍需加 -lpthread 选项,底层仍依赖 Linux 原生线程库。
4.1 重新理解原生线程库
Linux 中没有独立的线程概念,只有轻量级进程(LWP)。创建轻量级进程需调用 clone 系统接口。
- clone 函数参数:
- 函数指针:指向新的执行流(回调函数)。
- 栈地址:创建新线程时传入自定义栈。
- 共享标志:是否让地址空间实现共享。
pthread 线程库封装了 clone 接口,对外提供 pthread_create、pthread_join 等接口。
内存加载: 原生线程库是动态库,加载到内存后映射到共享区。线程的概念由库维护,线程库管理多个线程属性集合。
4.2 深度剖析原生线程库
线程的 TID 在 16 进制下存储的是线程控制块 (TCB) 信息的起始地址。每创建一个线程,线程库在共享区内开辟一段空间填充信息(独立栈、回调函数),并将 TCB 像数组一样管理。线程退出结果存储在线程局部存储区域,因此必须调用 pthread_join 获取。
每个线程有独立的调用链和栈帧结构,保留临时变量、传参及返回地址。非主线程的栈在库的共享区中维护,tid 指向用户的 TCB。
所有进程创建的线程都在共享区的动态库中进行管理。CPU 看到的 task_struct 如果是线程的,则执行流等于线程;如果是进程的,则执行流大于线程。用户级线程和内核级 LWP 共同构成 Linux 线程。
线程私有全局变量:
若线程需要私有全局变量,可使用 __thread 关键字修饰(如 __thread int g_val = 100;),即线程局部存储 (TLS)。注意 TLS 只能定义内置类型,不能修饰自定义类型。


