# 线程
线程(Thread)是进程运行、CPU 调度的最小单位。
- 每个进程可视作一个线程组,至少包含一个线程。其中第一个创建的线程称为主线程。
每个线程(称为父线程)可以创建任意个其它线程(称为子线程)。
- 父线程及其所有子线程属于同一个进程。
- 当父线程终止时,系统会自动杀死其所有子线程。
- 当一个进程中的所有线程都终止时,内核才认为该进程已终止,回收其占用的系统资源。
- 每个线程也可以创建其它进程,不过此时看作是该线程所属的进程创建了其它进程,考虑的是进程间关系,而不是线程间关系。
- 父线程及其所有子线程属于同一个进程。
# 共享资源
内核给一个进程分配了系统资源之后,大部分会被该进程下的各个线程共享,包括:
- PID、PPID
- User ID 、Group ID
- 内存,包括:
- 虚拟内存空间,寻址相同
- 堆区,包括静态变量、全局变量
- 打开的文件描述符,比如 Socket
- 环境变量,比如当前工作目录
- 进程间通信的 signals 和 signal handlers
每个线程也有独自分配的系统资源,包括:
- TID
- CPU 调度
- 栈区,比如局部变量
- 寄存器
# 相关函数
#include <pthread.h>
int pthread_create(pthread_t *id, // 传入用于存储线程 TID 的指针变量
const pthread_attr_t *attr, // 传入线程的属性(传入 NULL 则是默认属性)
void *(*)(void *), // 传入要运行的函数,该函数头的格式应该定义成:void *fun(void *arg)
void *arg); // 传入要运行的函数的实参,没有参数则填 NULL
// 用于创建一个线程,运行指定的函数
// 如果创建成功,则将线程的 TID 存储到变量 id 中,并返回 0
int pthread_join(pthread_t id, void **retval);
// 阻塞当前线程的运行,直到指定 TID 的线程终止
// 等目标线程终止时,会将其退出值存储到变量 retval 中
void pthread_exit(void *retval);
// 使当前线程退出,且退出码取值为 status
int pthread_cancel(pthread_t id);
// 向指定 TID 的线程发送取消请求
// 目标线程可能立即终止,也可能稍后终止,也可能忽略取消请求
- 编译时需要链接 pthread.h 库,例如:
gcc test.c -o test -l pthread
- 线程有两种属性:
- joinable :默认。当线程终止时,不会立即释放它占用的资源,而是等到其它线程调用 pthread_join() 来获取它的退出值。
- detached :当线程终止时,会立即释放它占用的资源。
- 调用 pthread_exit() 会终止当前线程,而调用 exit() 会终止当前进程的所有线程。
- 例:
#include <stdio.h> #include <pthread.h> void *fun1(){ puts("fun1() end."); return 0; } int main(){ int rc; pthread_t id; rc = pthread_create(&id, NULL, fun1, NULL); if(rc) puts("Failed to create the thread fun1()."); pthread_join(id, NULL); // 阻塞主线程的运行,以免主线程运行结束时提前终止子线程 puts("main() end."); return 0; }
# LWP
- Linux 系统上,用户可以调用 pthread.h 库的
pthread_create()
函数创建线程,不过实际创建的是轻量级进程(Light Weight Process ,LWP)。- 每个进程可以创建多个 LWP ,它们之间共享系统资源,因此可以模拟线程。
- 每个进程中,第一个创建的 LWP (即主线程)的 TID ,总是等于该进程的 PID 。