Linux 线程通信 学习笔记
Linux线程本质
•在Linux内核中,没有独立的“线程”数据结构,线程的本质是“共享资源的轻量级进程(LightweightProcess,简称LWP)”;
•内核视角:每个线程对应一个“任务控制块(task_struct)”,内核通过task_struct管理调度单元;
•资源共享:多个LWP(线程)通过共享同一“进程地址空间(mm_struct)、文件描述符(files_struct)、信号处理表(signal_struct)”等核心资源,实现“线程共享进程资源”的特性;
•与传统进程的区别:普通进程的task_struct对应独立的mm_struct(独立地址空间),而线程的task_struct共享同一mm_struct,本质上是“同一进程地址空间下的多个调度单元”;
线程的状态
在Linux系统中,线程作为内核调度的基本单元(轻量级进程),会因资源占用、执行阶段不同,呈现多种状态;
•运行状态(R):也称之为可运行状态,就是处于运行状态的线程,正在CPU执行或等待CPU调度;
•可中断睡眠状态(S):线程在等待某个事件(如等待I/O操作、等待特定信号等)完成,可被信号唤醒;
•不可以中断睡眠状态(D):线程正在等待关键硬件操作完成,不能被信号中断,确保数据完整性;
•停止状态(T):线程被暂停执行,通常是收到了特定的信号(如SIGSTOP(暂停线程))或者在调试程序时被调试器暂停,可被信号恢复执行(如SIGCONT);
•僵尸状态(Z):线程已终止,相关资源未被回收,等待所属进程(或其他负责线程资源回收的机制)进行回收;
•死亡态(X):线程彻底终止,资源全部释放,是一个瞬间状态,几乎无法观测到;
Linux线程与POSIX标准的兼容
•标准遵循:Linux 原生 POSIX 线程库(libpthread)完全遵循 POSIX 线程标准,提供与其他 UNIX-like系统一致的线程接口,确保多平台代码可移植性;
•核心接口与库依赖:线程创建(pthread_create)、线程等待(pthread_join)等核心操作,均由libpthread.so 库提供;
•编码时需包含头文件:#include <pthread.h>(声明库中函数接口);
•编译时需显式链接线程库:gcc 源码.c -o 可执行文件 -lpthread(否则会报 “未定义引用” 错误);
•内核交互方式:Linux 下操作线程需通过 libpthread 库的 POSIX 接口,而非直接调用内核系统调用;用户态代码通过库封装与内核交互;
进程与线程的核心区别
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源独立性 | 拥有独立的内存空间、文件描述符等系统资源 | 共享所属进程的内存空间和资源,仅拥有独立的栈、程序计数器等 |
| 创建/切换开销 | 开销大(涉及资源分配与回收) | 开销小(仅需保存少量上下文信息) |
| 通信方式 | 需通过进程间通信(IPC)机制(如信号、消息队列等等) | 可直接通过共享内存通信(需同步机制避免冲突) |
| 健壮性 | 一个进程崩溃通常不影响其他进程 | 一个线程崩溃可能导致整个进程崩溃 |
| 适用场景 | 任务间需要严格隔离(如浏览器标签页); 充分利用多核CPU处理CPU密集型任务(如大规模计算); 长时间运行的独立任务(如后台备份、视频转码); 运行不稳定或第三方代码(避免崩溃影响整体); | 任务间需要频繁共享数据(如Web服务器处理多请求); 轻量级并发,追求低开销(如GUI程序后台加载数据); I/O密集型任务(如网络请求、文件读写,可利用等待时间切换); 短期、协作紧密的子任务(如数据分片处理后汇总); |
线程创建与终止函数
pthread_create函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); | |
| 功能 | 在调用进程中创建一个新的线程 | |
| 参数 | thread | 用于存储新创建线程的唯一标识符(线程ID) |
attr | 用于指定新线程的属性,若为NULL,则使用系统默认属性 | |
start_routine | 指向线程的入口函数 | |
arg | 用于给线程入口函数传递参数,若无需参数,可设为NULL | |
| 返回值 | - 成功:返回0,新线程ID会写入thread指向的变量中- 失败:返回非 0错误码 | |
pthread_exit函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | void pthread_exit(void *retval); | |
| 功能 | 结束当前线程 | |
| 参数 | retval | 用来给回收资源的线程传递线程的退出状态值; 若不想返回退出状态值,可以传 |
| 返回值 | 无 | |
线程标识函数
pthread_self函数
| 项目 | 内容 |
|---|---|
| 所需头文件 | #include <pthread.h> |
| 函数原型 | pthread_t pthread_self(void); |
| 功能 | 获取调用线程的线程ID |
| 参数 | 无 |
| 返回值 | 成功:总是成功,返回调用线程的线程ID |
线程回收函数
pthread_join函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_join(pthread_t thread, void **retval); | |
| 功能 | 阻塞等待指定的已终止线程结束,并为其回收资源 | |
| 参数 | thread | 要回收资源的线程ID |
| retval | 用来接收线程退出的状态值,不关心则传NULL | |
| 返回值 | 成功返回0,失败返回非零错误码 | |
资源回收管理模式
•线程创建后默认处于结合态,若未显示设置为分离态,其终止后资源不会立即释放,必须由其他线程(通常是主线程),通过pthread_join( )主动回收;
•分离态的线程,终止后会由系统自动回收资源,无需其他线程调用pthread_join( );
| 对比维度 | 结合态(默认) | 分离态 |
|---|---|---|
| 资源回收方式 | 必须通过其他线程调用pthread_join()回收 | 线程终止后,系统自动回收资源 |
pthread_join()作用 | 有效:调用后阻塞,直到目标线程终止并回收其资源 | 无效:调用会返回错误,无法等待分离态线程 |
| 未回收的风险 | 若主线程未调用pthread_join(),线程终止后会成为“僵尸线程”,占用task_struct等资源 | 无风险:终止后资源立即释放,不会产生僵尸线程 |
| 状态设置时机/方式 | 创建时默认(无需额外操作);也可通过pthread_detach()函数从结合态转为分离态 | 在pthread_create()的attr参数中设置PTHREAD_CREATE_DETACHED |
| 适用场景 | 需要获取线程退出状态的场景 | 无需获取线程退出状态、希望自动释放资源的场景 |
pthread_attr_init函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_attr_init(pthread_attr_t *attr); | |
| 功能 | 将一个线程属性对象初始化为默认值 | |
| 参数 | attr | 需要被初始化的线程属性对象 |
| 返回值 | 成功:返回0 失败:返回非0错误码 | |
pthread_attr_setdetachstate函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); | |
| 功能 | 设置线程属性对象的分离状态 | |
| 参数 | attr | 设置分离状态的线程属性对象 |
detachstate | 用于指定线程的分离状态 - PTHREAD_CREATE_JOINABLE:表示将线程设置为结合态- PTHREAD_CREATE_DETACHED:表示将线程设置为分离态 | |
| 返回值 | 成功:返回0 失败:返回非0错误码 | |
pthread_attr_destroy函数
| 项目 | 说明 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_attr_destroy(pthread_attr_t *attr); | |
| 功能 | 销毁一个已初始化的线程属性对象 | |
| 参数 | attr | 指定需要被销毁的线程属性对象 |
| 返回值 | 成功:返回 0;失败:返回非 0 错误码 | |
线程控制函数
pthread_detach函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_detach(pthread_t thread); | |
| 功能 | 标记一个线程为分离态 | |
| 参数 | thread | 线程id |
| 返回值 | 成功:返回0;失败:返回非0错误码 | |
pthread_cancel函数
| 项目 | 说明 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_cancel(pthread_t thread); | |
| 功能 | 请求取消指定的线程。目标线程对取消请求的处理取决于其取消状态和取消类型。 | |
| 参数 | thread | 要取消的线程ID |
| 返回值 | 成功:返回0;失败:返回非0错误码 | |
pthread_setcancelstate函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_setcancelstate(int state, int *oldstate); | |
| 功能 | 设置调用线程的可取消状态 | |
| 可取消状态 | PTHREAD_CANCEL_ENABLE:可被取消(默认)PTHREAD_CANCEL_DISABLE:不可被取消 | |
| 参数 | state | 新的可取消状态 |
oldstate | 用于保存旧的可取消状态(可为NULL) | |
| 返回值 | 成功:返回 0 失败:返回非 0 错误码 | |
pthread_setcanceltype函数
| 项目 | 内容 | |
|---|---|---|
| 头文件 | #include <pthread.h> | |
| 原型 | int pthread_setcanceltype(int type, int *oldtype); | |
| 功能 | 设置调用线程的可取消类型 | |
| 可取消类型 | -PTHREAD_CANCEL_DEFERRED:延时取消(默认),线程直到下一次调用可作为取消点的函数时才被取消。- PTHREAD_CANCEL_ASYNCHRONOUS:异步取消,线程可被立即取消。 | |
| 参数 | type | 要设置的新的可取消类型值。 |
oldtype | 存储旧的可取消类型值。 | |
| 返回值 | - 成功:返回0。- 失败:返回非 0的错误码。 | |
互斥锁
•互斥锁(Mutex,即Mutual Exclusion Lock)是并发编程中用于保护 “共享资源” 的同步工具,本质是一把“独占钥匙”;
•为什么需要互斥锁?
•当多个线程同时读写共享资源时,会出现 “竞态条件”,导致数据错误;
•互斥锁能强制线程 “排队访问” 资源,避免竞争;
•核心作用:保证 “同一时刻,只有一个线程能持有锁并访问共享资源”,实现 “互斥访问”;
pthread_mutex_init函数
| 分类 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); | |
| 功能 | 动态初始化互斥锁 | |
| 参数 | mutex | 指定要进行初始化的互斥锁变量 |
attr | 设置互斥锁的属性,若传
| |
| 返回值 | 成功:返回 0 失败:返回非 0 错误码 | |
pthread_mutex_lock函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_mutex_lock(pthread_mutex_t *mutex); | |
| 功能 | 用于获取互斥锁。当互斥锁已被其他线程获取时,调用该函数的线程会被阻塞,直到互斥锁可用并成功获取。 | |
| 参数 | mutex | 互斥锁 |
| 返回值 | 成功:返回0;失败:返回非0错误码 | |
pthread_mutex_trylock函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_mutex_trylock(pthread_mutex_t *mutex); | |
| 功能 | 用于获取互斥锁;当互斥锁已被其他线程获取时,调用该函数的线程不会被阻塞,而是立即返回错误。 | |
| 参数 | mutex | 互斥锁 |
| 返回值 | 成功:返回0;失败:返回非0错误码 | |
pthread_mutex_unlock函数
| 项目 | 说明 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_mutex_unlock(pthread_mutex_t *mutex); | |
| 功能 | 解锁 | |
| 一般使用时 | 只有加锁的线程可以解锁 | |
| 参数 | mutex | 互斥锁 |
| 返回值 | 成功:返回 0;失败:返回非 0 错误码 | |
pthread_mutex_destroy函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_mutex_destroy(pthread_mutex_t *mutex); | |
| 功能 | 销毁互斥锁 | |
| 参数 | mutex | 互斥锁 |
| 返回值 | 成功:返回0 失败:返回非0错误码 | |
条件变量
•条件变量是并发编程中用于实现线程间“等待-通知”机制的同步工具,可让线程在某个条件不满足时挂起,待条件满足后被唤醒;
•为什么需要条件变量?
•仅用互斥锁能保证共享资源访问的互斥性,但无法解决 “线程需等待特定条件成立才能继续执行” 的场景(比如生产者 - 消费者模型中,消费者需等待生产者生产出数据才能消费);
•条件变量可结合互斥锁,让线程在条件不满足时主动释放锁并等待,条件满足时被唤醒以重新获取锁继续执行,灵活应对依赖条件的同步需求;
•核心作用:实现线程间的 “条件等待与通知”,使线程能在特定条件下协作,避免线程忙等情况消耗 CPU资源,提升并发效率;
pthread_cond_init函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); | |
| 功能 | 动态初始化条件变量 | |
| 参数 | cond | 指定要初始化的条件变量 |
attr | 设置条件变量的属性,若为
| |
| 返回值 | 成功:返回0失败:返回非 0错误码 | |
pthread_cond_signal函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_cond_signal(pthread_cond_t *cond); | |
| 功能 | 唤醒至少一个等待在指定条件变量cond上的线程。 | |
| 参数 | cond | 指向要操作的条件变量。 |
| 返回值 | 成功:返回0失败:返回非 0的错误码(该错误码用于指示错误类型)。 | |
pthread_cond_broadcast函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_cond_broadcast(pthread_cond_t *cond); | |
| 功能 | 唤醒所有等待在指定条件变量cond上的线程 | |
| 参数 | cond | 指定要操作的条件变量 |
| 返回值 | 成功:返回0失败:返回非 0错误码 | |
pthread_cond_wait函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); | |
| 功能 | 线程阻塞在条件变量cond上,等待被唤醒;同时,该函数会自动释放传入的互斥锁mutex,当线程被唤醒时,又会自动重新获取该互斥锁;保证了线程在等待条件过程中互斥锁的正确管理,避免死锁等问题。 | |
| 参数 | cond | 用于指定线程等待的条件变量 |
mutex | 与条件变量配合使用,保证线程在等待条件时的互斥 | |
| 返回值 | 成功:返回 0 失败:返回非 0 错误码 | |
pthread_cond_destroy函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <pthread.h> | |
| 原型 | int pthread_cond_destroy(pthread_cond_t *cond); | |
| 功能 | 销毁条件变量相关的资源 | |
| 参数 | cond | 要销毁的条件变量 |
| 返回值 | 成功:返回0;失败:返回非0错误码 | |
无名信号量
•无名信号量是一种用于线程同步或进程同步的工具,通过维护一个计数值来控制对共享资源的访问,可限制同时访问资源的线程或进程数量;
•为什么需要无名信号量?
•互斥锁只能保证同一时刻一个线程访问资源,而有些场景需要允许多个线程同时访问;
•无名信号量的计数值可灵活设置,能实现 “多线程有限并发访问” 或 “线程间的计数同步”,补充互斥锁在多资源并发场景的不足;
•核心作用:通过计数值控制共享资源的并发访问数量,支持多线程在一定限制下同时访问资源,或实现线程间基于计数的同步协作;
sem_init函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <semaphore.h> | |
| 原型 | int sem_init(sem_t *sem, int pshared, unsigned int value); | |
| 功能 | 初始化无名信号量 | |
| 参数 | sem | 要初始化的信号量指针 |
pshared | -0:线程间同步(同一进程内多个线程)- ≠0:进程间同步(需将信号量放在共享内存中) | |
value | 信号量初始计数值(可用资源数量) - 1:互斥锁(一次允许一个线程/进程)- 0:初始无资源,调用sem_wait会阻塞 | |
| 返回值 | 成功返回0;失败返回-1,并设置errno | |
sem_wait函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <semaphore.h> | |
| 原型 | int sem_wait(sem_t *sem); | |
| 功能 | 信号量的值减 1。 • 如果信号量的当前值大于 0,减 1 后函数直接返回(表示“获取资源成功”)。 • 如果信号量的当前值等于 0, sem_wait会阻塞,直到有其他线程/进程调用sem_post函数,使得信号量值大于 0 后,再执行“减 1”并返回。 | |
| 参数 | sem | 无名信号量 |
| 返回值 | 成功:返回 0 失败:返回 -1,并设置错误码(不会改变信号量的值) | |
sem_post函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <semaphore.h> | |
| 原型 | int sem_post(sem_t *sem); | |
| 功能 | 释放信号量(将信号量的值 +1) | |
| 参数 | sem | 用于指定对哪个信号量进行释放操作 |
| 返回值 | 成功:返回 0;失败:返回 -1,并设置错误码(不会改变信号量的值) | |
sem_destroy函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <semaphore.h> | |
| 原型 | int sem_destroy(sem_t *sem); | |
| 功能 | 销毁无名信号量,释放该信号量相关的资源 | |
| 参数 | sem | 指定对哪个信号量进行销毁操作 |
| 返回值 | 成功:返回0 失败:返回-1,并设置错误码 | |
线程的互斥与同步应用场景
| 工具 | 应用场景特点 | 优势 | 局限性 |
|---|---|---|---|
| 互斥锁 | 单一共享资源,同一时刻只允许一个线程访问;主要用于防止数据竞争和不一致性。 | 实现简单,易于理解和使用;能有效解决单一资源的互斥访问问题,保障数据的一致性和完整性。 | 若线程长时间持有锁,可能导致其他线程长时间等待,降低系统并发性能;使用不当容易产生死锁(例如多个线程循环等待对方持有的锁)。 |
| 条件变量 | 线程需要在某个条件满足时才能继续执行;配合互斥锁,实现线程间的等待-通知机制。 | 避免线程的忙等待,有效节省 CPU 资源,提高程序运行效率;可以实现更灵活的线程同步逻辑,满足复杂的条件等待和协作需求。 | 必须与互斥锁配合使用,条件的判断和通知的时机需要准确把握,否则可能导致线程无法正确唤醒或错过唤醒,影响程序正确性。 |
| 无名信号量 | 限制对共享资源的并发访问数量;可用于线程或进程的同步和计数控制。 | 可以灵活控制并发访问的资源数量,适用于需要对资源进行定量管理的场景;不仅能实现互斥,还能进行计数同步,功能比互斥锁更丰富。 | 信号量的计数管理需要准确设置和维护,容易出现资源过度使用或浪费的情况。 |
