news 2026/4/16 14:18:38

Linux 线程控制核心:互斥锁与信号量(同步)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 线程控制核心:互斥锁与信号量(同步)

一、互斥锁:临界资源的排他性访问

1.1 核心概念

(1)临界资源

多线程中会被读写操作的共享资源,常见类型:

  • 全局变量、静态变量;
  • 文件、设备(如串口、网卡);
  • 其他可被多线程访问的共享内存 / 句柄。
(2)排他性访问

同一时刻,仅允许一个线程对临界资源进行读写操作 —— 这是互斥锁的核心目标。

(3)为什么需要互斥?

多线程并发执行时,代码是「穿插调度」的。以简单的a++为例:

c

运行

a++; // 看似一行代码,汇编至少分3步: // 1. 从内存读取a的值到寄存器; // 2. 寄存器中a的值+1; // 3. 寄存器值写回内存a。

若线程 1 执行完前 2 步后被调度切走,线程 2 执行完整 3 步,再切回线程 1 执行第 3 步 —— 最终a只加了 1 次,而非预期的 2 次,导致数据一致性错误

互斥锁的作用就是将这段代码变成「原子操作」:加锁后,这段代码必须在一次线程调度中完整执行,不可被打断。

1.2 互斥锁的使用步骤

互斥锁的生命周期遵循「定义→初始化→加锁→解锁→销毁」的固定流程,缺一不可。

(1)核心函数(POSIX 标准)
操作函数原型关键说明
定义pthread_mutex_t mutex;声明互斥锁变量(全局 / 局部均可,需保证所有线程可见)
初始化int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);attr 传 NULL 表示默认属性;成功返回 0,失败返回非 0
加锁int pthread_mutex_lock(pthread_mutex_t *mutex);加锁失败则阻塞(等待其他线程解锁);成功返回 0
解锁int pthread_mutex_unlock(pthread_mutex_t *mutex);必须与加锁线程为同一个;成功返回 0
销毁int pthread_mutex_destroy(pthread_mutex_t *mutex);锁未解锁时销毁会报错;成功返回 0
(2)核心规则
  • 加锁和解锁必须由同一个线程执行;
  • 临界区(加锁→解锁之间的代码)必须「短小精悍」:
    • 禁止在临界区中执行sleep、IO 等耗时操作;
    • 临界区代码越短,多线程并发效率越高。
(3)基础示例:互斥锁保护全局变量

c

运行

#include <stdio.h> #include <pthread.h> #include <unistd.h> int a = 0; pthread_mutex_t mutex; void* th_func(void* arg) { for (int i = 0; i < 10000; i++) { pthread_mutex_lock(&mutex); // 加锁 a++; // 临界区:仅一行,原子操作 pthread_mutex_unlock(&mutex); // 解锁 } return NULL; } int main() { pthread_t tid1, tid2; pthread_mutex_init(&mutex, NULL); // 初始化锁 pthread_create(&tid1, NULL, th_func, NULL); pthread_create(&tid2, NULL, th_func, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(&mutex); // 销毁锁 printf("最终a的值:%d\n", a); // 预期输出20000 return 0; }

二、信号量:带顺序的同步访问

2.1 核心概念

(1)同步的定义

「有先后顺序的排他性访问」—— 不仅要保证同一时刻一个资源被一个线程访问,还要强制线程按照指定顺序执行(如线程 A 执行完后,线程 B 才能执行)。

(2)与互斥锁的关系
  • 互斥包含同步:同步是互斥的「特例」(互斥只保证排他,同步额外保证顺序);
  • 释放逻辑不同:
    • 互斥锁:加锁 / 解锁是同一个线程;
    • 信号量:线程 A 释放资源,线程 B 申请资源(交叉释放)。
(3)计数信号量的用途

信号量初始值可大于 1(如 3、5),适用于「多份相同资源」的竞争场景(如你之前代码中win=3的有限资源竞争)。

2.2 信号量的使用步骤

信号量生命周期:「定义→初始化→PV 操作→销毁」。

(1)核心函数(POSIX 标准)
操作函数原型关键说明
定义sem_t sem;声明信号量变量
初始化int sem_init(sem_t *sem, int pshared, unsigned int value);pshared=0(线程间使用)、!=0(进程间);value = 初始资源数;成功返回 0,失败 - 1
P 操作(申请资源)int sem_wait(sem_t *sem);sem>0 则 sem-1 并继续;sem=0 则阻塞;成功返回 0,失败 - 1
V 操作(释放资源)int sem_post(sem_t *sem);sem+1,不阻塞;成功返回 0,失败 - 1
销毁int sem_destroy(sem_t *sem);成功返回 0,失败 - 1
(2)核心规则
  • P 操作(sem_wait):申请资源,资源数 - 1;
  • V 操作(sem_post):释放资源,资源数 + 1;
  • 信号量临界区可包含短暂休眠 / 耗时操作(比互斥锁灵活)。
(3)基础示例:信号量实现线程同步

c

运行

#include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> sem_t sem; // 信号量:控制线程执行顺序 void* th1(void* arg) { printf("线程1:执行初始化操作\n"); sleep(2); sem_post(&sem); // V操作:释放资源(sem=1) return NULL; } void* th2(void* arg) { sem_wait(&sem); // P操作:等待资源(sem=0时阻塞) printf("线程2:线程1初始化完成后执行\n"); return NULL; } int main() { pthread_t tid1, tid2; sem_init(&sem, 0, 0); // 初始化:线程间使用,初始值0 pthread_create(&tid1, NULL, th1, NULL); pthread_create(&tid2, NULL, th2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); sem_destroy(&sem); return 0; }

三、互斥锁 vs 信号量(核心区别)

维度互斥锁信号量
核心目标排他性访问(无顺序)有序的排他性访问(同步)
释放主体加锁线程自己解锁线程 A 申请,线程 B 释放(交叉)
初始值无(只有锁定 / 未锁定状态)可设为任意非负整数(如 0、1、3)
临界区限制禁止耗时操作,必须短小可包含短暂休眠 / 小耗时操作
适用场景单一资源的排他访问(如全局变量)多资源竞争、线程顺序控制

四、死锁:线程控制的 “致命陷阱”

4.1 死锁的定义

因锁资源的申请 / 释放逻辑错误,导致线程 / 进程永久阻塞,无法继续执行的现象。

4.2 死锁的四个必要条件(缺一不可)

  1. 互斥条件:一个资源每次只能被一个线程使用;
  2. 请求与保持条件:线程因申请资源阻塞时,不释放已持有的资源;
  3. 不剥夺条件:线程已获得的资源,未使用完前不能被强行剥夺;
  4. 循环等待条件:多个线程形成「A 等 B 的资源,B 等 C 的资源,C 等 A 的资源」的循环。

4.3 死锁规避技巧

  1. 锁的申请顺序一致:所有线程按「锁 1→锁 2→锁 3」的顺序申请,避免循环等待;
  2. 加锁限时:使用pthread_mutex_timedlock替代pthread_mutex_lock,超时则放弃;
  3. 避免嵌套锁:尽量减少锁的嵌套使用,嵌套越多,死锁风险越高;
  4. 及时解锁:临界区执行完毕立即解锁,不持有锁休眠。

五、实战场景:有限资源的多线程竞争

以你之前的「win=3 资源竞争」场景为例,用信号量替代互斥锁,更贴合 “多资源竞争” 的需求:

c

运行

#include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> #include <stdlib.h> #include <time.h> int a = 0; sem_t sem; // 信号量:初始值3,代表3个可用资源 void* th_func(void* arg) { while (1) { sem_wait(&sem); // P操作:申请资源(资源数-1) // 临界区:可包含短暂休眠 printf("线程%lu:获取资源\n", pthread_self()); sleep(rand() % 2); a++; printf("线程%lu:释放资源,a=%d\n", pthread_self(), a); sem_post(&sem); // V操作:释放资源(资源数+1) if (a >= 10) break; // 退出条件 } return NULL; } int main() { pthread_t tid[10]; srand((unsigned int)time(NULL)); sem_init(&sem, 0, 3); // 初始化:线程间使用,初始资源数3 for (int i = 0; i < 10; i++) { pthread_create(&tid[i], NULL, th_func, NULL); } for (int i = 0; i < 10; i++) { pthread_join(tid[i], NULL); } sem_destroy(&sem); printf("最终a的值:%d\n", a); return 0; }

六、核心总结

  1. 互斥锁:解决「单一资源的排他访问」,临界区必须短小,加解锁同线程;
  2. 信号量:解决「多资源竞争 / 线程同步」,支持交叉释放,临界区可适度耗时;
  3. 死锁规避:打破四个必要条件中的任意一个(如统一锁申请顺序)即可;
  4. 选型原则:
    • 单一资源排他访问 → 互斥锁;
    • 多资源竞争 / 线程顺序控制 → 信号量。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:14:37

Legado书源规则终极指南:从零开始打造专属阅读源

还在为找不到心仪的阅读资源而苦恼吗&#xff1f;想要拥有完全自定义的阅读体验吗&#xff1f;Legado书源规则就是你的答案&#xff01;掌握Legado书源规则编写技巧&#xff0c;意味着你能够自由获取网络上的任何内容&#xff0c;打造真正属于你的阅读世界。在接下来的100字内&…

作者头像 李华
网站建设 2026/4/14 20:47:34

Advance Steel 2026安装教程安装教程及下载

下载链接&#xff1a;https://docs.qq.com/aio/DSXN5aGhyVkVDdmZp软件介绍Advance Steel是一款专为钢结构工程打造的3D建模与深化设计软件&#xff0c;基于AutoCAD平台构建&#xff0c;具备直观易用的操作界面&#xff0c;支持所有AutoCAD功能&#xff0c;便于工程师快速上手。…

作者头像 李华
网站建设 2026/4/16 10:41:06

基于灰色预测模型的预测算法研究:探讨不确定性与数学建模的交融

预测算法一&#xff1a;灰色预测模型灰色预测是对含有已知信息又含有不确定信息的系统进行预测&#xff0c;就是对一定范围内变化的、与时间有关的灰色过程进行预测&#xff1b; 注&#xff1a;1、提供灰色预测相关论文已经学习资料&#xff0c;提供数学建模指导 2、Matlab代码…

作者头像 李华
网站建设 2026/4/15 16:38:32

嵌入式存储革命:littlefs核心架构深度解析与实战应用

嵌入式存储革命&#xff1a;littlefs核心架构深度解析与实战应用 【免费下载链接】littlefs A little fail-safe filesystem designed for microcontrollers 项目地址: https://gitcode.com/GitHub_Trending/li/littlefs 在当今嵌入式系统蓬勃发展的时代&#xff0c;微控…

作者头像 李华
网站建设 2026/4/15 3:43:08

GLM-4.5-FP8大模型快速部署指南:从零到精通

GLM-4.5-FP8是智谱AI推出的3550亿参数混合专家大语言模型&#xff0c;采用创新的FP8精度格式&#xff0c;为开发者提供高效推理解决方案。本文面向技术新手和普通开发者&#xff0c;通过实操步骤和成本分析&#xff0c;帮助您快速掌握这一前沿AI技术。 【免费下载链接】GLM-4.5…

作者头像 李华