当前位置: 首页 > news >正文

学习笔记——线程控制 - 互斥与同步

线程控制 - 互斥与同步

一、 互斥(Mutex)

1.概念

在多线程中对临界资源的排他性访问。

  • 临界资源:在多线程中会被多个线程进行读写操作的资源(全局变量、文件、设备等)

  • 排他访问:同一时刻只能有一个线程进行读写操作

2.用途

在多线程中,一个资源同一时刻只能有一个线程访问。

示例问题

int A = 0; // 临界资源 // 线程1 void* th1(void* arg) { A++; // 这不是原子操作! } // 线程2 void* th2(void* arg) { A++; }

A++在汇编中至少需要3步:

  1. 读取A到寄存器

  2. 寄存器值加1

  3. 将结果写回A

如果th1执行了1、2步后切换到th2,就会发生数据一致性问题。

解决方案:使用互斥锁

使用步骤

  1. 定义互斥锁

  2. 初始化锁

  3. 加锁

  4. 解锁

  5. 销毁

相关函数

1. 定义互斥锁
pthread_mutex_t mutex;
2. 初始化锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • 功能:初始化互斥锁

  • 参数:

    • mutex:要初始化的互斥锁

    • attr:初始化属性,一般为NULL(默认锁)

  • 返回值:成功返回0,失败返回非0

3. 加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 功能:给代码加锁

  • 特点:

    • 加锁到解锁之间的代码属于原子操作

    • 在加锁期间其他线程不能执行该部分代码

    • 如果锁已被占用,线程会阻塞等待

  • 返回值:成功返回0,失败返回非0

4. 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 功能:解锁互斥锁

  • 注意:加锁和解锁一般成对出现

  • 返回值:成功返回0,失败返回非0

5. 销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 功能:销毁互斥锁

  • 返回值:成功返回0,失败返回非0

示例代码

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> int A = 0 ; pthread_mutex_t mutex; void* th(void* arg) { // pthread_mutex_lock(&mutex); int i = 5000; while(i--) { pthread_mutex_lock(&mutex); int tmp = A; printf("A is %d\n",tmp+1); A = tmp+1; pthread_mutex_unlock(&mutex); } //pthread_mutex_unlock(&mutex); return NULL; } int main(int argc, char **argv) { pthread_t tid1,tid2; pthread_mutex_init(&mutex,NULL); pthread_create(&tid1,NULL,th,NULL); pthread_create(&tid2,NULL,th,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); pthread_mutex_destroy(&mutex); return 0; }

2. 同步(Synchronization)

概念

按照一定先后顺序对资源的排他性访问。

与互斥的关系

  • 互斥包含同步,同步是互斥的一个特例

  • 互斥:只关心资源是否被占用

  • 同步:不仅关心资源,还关心访问顺序

3. 信号量(Semaphore)

与互斥锁的区别

  1. 加锁/解锁主体:

    • 互斥锁:加锁和解锁必须是同一个线程

    • 信号量:可以由不同线程交叉释放(th1释放th2,th2释放th1)

  2. 使用场景:

    • 互斥锁:临界区代码要短小精悍,不要有休眠或耗时操作

    • 信号量:可以有适当的休眠和小耗时操作

计数信号量

信号量初值可以大于1,用于多个资源的情况。

使用步骤

  1. 定义信号量

  2. 初始化信号量

  3. PV操作

  4. 销毁信号量

相关函数

1. 定义信号量
sem_t sem;
2. 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
  • 功能:初始化信号量

  • 参数:

    • sem:要初始化的信号量

    • pshared:

      • 0:线程间使用

      • 非0:进程间使用

    • value:信号量初始值

      • 二值信号量:0或1

      • 0:红灯,线程阻塞

      • 1:绿灯,线程可通过

  • 返回值:成功返回0,失败返回-1

3. PV操作
  • P操作:申请资源 →sem_wait()

  • V操作:释放资源 →sem_post()

sem_wait()

int sem_wait(sem_t *sem);
  • 功能:申请信号量资源

  • 行为:

    • 如果有资源(>0),申请资源,继续执行

    • 如果没资源(=0),线程阻塞等待

  • 注意:自动执行sem = sem - 1

  • 返回值:成功返回0,失败返回-1

sem_post()
int sem_post(sem_t *sem);
  • 功能:释放信号量资源

  • 注意:自动执行sem = sem + 1

  • 返回值:成功返回0,失败返回-1

4. 销毁信号量
int sem_destroy(sem_t *sem);

示例代码:

#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include <semaphore.h> sem_t sem_H,sem_W; void* th1(void* arg) { int i = 10; while(i--) { sem_wait(&sem_H);// sem_H -1 printf("hello "); fflush(stdout); sem_post(&sem_W);// sem_W +1 } return NULL; } void* th2(void* arg) { int i = 10; while(i--) { sem_wait(&sem_W); printf("world\n"); sleep(1); sem_post(&sem_H); } return NULL; } int main(int argc, char **argv) { pthread_t tid1,tid2; sem_init(&sem_H,0,1); sem_init(&sem_W,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_H); sem_destroy(&sem_W); return 0; }

4. 死锁(Deadlock)

概念

由于锁资源安排不合理,导致进程/线程无法继续执行的现象。

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放

  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺

  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

死锁示例

pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER; // 线程1 void* thread1(void* arg) { pthread_mutex_lock(&mutexA); // 获取锁A sleep(1); pthread_mutex_lock(&mutexB); // 尝试获取锁B → 死锁! // ... pthread_mutex_unlock(&mutexB); pthread_mutex_unlock(&mutexA); return NULL; } // 线程2 void* thread2(void* arg) { pthread_mutex_lock(&mutexB); // 获取锁B sleep(1); pthread_mutex_lock(&mutexA); // 尝试获取锁A → 死锁! // ... pthread_mutex_unlock(&mutexA); pthread_mutex_unlock(&mutexB); return NULL; }

5. 总结对比

特性互斥锁(Mutex)信号量(Semaphore)
用途保护临界区,确保互斥访问控制资源访问数量
资源数通常保护单个资源可以保护多个相同资源
加锁/解锁必须由同一线程完成可由不同线程完成
阻塞锁被占用时线程阻塞资源数为0时线程阻塞
计数无计数功能有计数功能
性能轻量级,适用于短临界区稍重,适用于较复杂同步

使用原则

  1. 能用互斥锁就用互斥锁,因为它更简单高效

  2. 临界区要短小,不要包含耗时操作

  3. 避免死锁:按固定顺序申请锁,或使用超时机制

  4. 锁的粒度要合适:不要过大(影响并发)或过小(增加开销)

http://www.cnnetsun.cn/news/115203.html

相关文章:

  • 基于改进蛇优化算法(GOSO/ISO)优化随机森林数据回归预测模型(含初始化种群混沌映射、减法...
  • 基于大数据的人脸识别系统设计与实现开题报告
  • 车载 Android 系统稳定性问题全解析:从性能到黑屏的排查指南
  • 气象在线监测系统助力智慧环境管理,金叶仪器专业气象监测解决方案
  • 【TVM 教程】交叉编译与 RPC
  • 腾讯云国际站代理商的QAPM服务能提供哪些专属服务?
  • 网安副业怎么选?漏洞挖掘、技术博客、竞赛奖金实战,哪个更适配你?
  • 量子计算验证方法:软件测试从业者的转型指南
  • 突破 Oracle/MySQL 瓶颈:金仓数据库以三重革新,筑牢业务转型 “数据底座”
  • 【学习神器】NotebookLM“播客”功能实战指南:四六级、考研党高效复习秘籍
  • 如何解决 pip install 网络报错 ERROR: No matching distribution found for requests
  • 12 Ways to Find User Account Info and Login Details in Linux
  • 紧急警告:错误的导出格式正毁掉你的量子实验成果,速查正确方式
  • 35 岁职场焦虑蔓延?为什么网络安全行业越老越值钱?
  • 内网渗透实战干货:12 个优质靶场平台精选,附避坑指南 + 实操技巧合集!
  • 新型电力系统下多分布式电源接入配电网承载力评估方法研究附Matlab代码
  • 50天学习FPGA第16天-verilog的模块与端口
  • 50天学习FPGA第15天-verilog基本概念
  • 基于Docker容器化部署Lsky Pro私有图床系统
  • GRPO不香了?小米ICPO横空出世,专治大模型“不会思考”,推理能力飙升!
  • Windows找不到xenroll.dll文件 如何下载修复?
  • 软件测试文档标准化编写指南
  • Paperzz AI:毕业论文写作的 “隐形助攻”,让学术输出告别 “抓瞎”
  • BypassAV通过Patch白文件实现Bypass,没有添加其他免杀手法
  • 鸿蒙:一个操作系统的生态远征与多行业渗透之路
  • 游戏启动缺少X3DAudio1_3.dll文件问题 下载修复
  • java毕业设计之基于数据安全的旅游民宿租赁系统源代码(java+springboot+mysql)
  • 基于SpringAI构建大模型应用
  • 黑锋科技(HeifengTech)过压过流保护开关芯片全系列技术解析
  • DVWA -SQL Injection-通关教程-完结