当前位置: 首页 > 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.jsqmd.com/news/106636/

相关文章:

  • 什么是智能体工程Agent Engineering?
  • SpringBoot使用设计模式一观察者模式
  • PHOTO1111
  • Vue3利用ResizeObserver监听Textarea的尺寸动态调整表格tbody的maxHeight
  • 论文文献引用格式最新规范流出,毕业季限时必看!
  • 大模型面试必备03——llama文章精读
  • TikTok多账号风控:找对安全支点,解锁规模化运营
  • 基于大数据的社交网络隐私保护及舆情分析可视化系统课题申报表
  • CUDA初始团队成员锐评cuTile「专打」Triton,Tile范式能否重塑GPU编程生态竞争格局
  • SpringBoot使用设计模式一装饰器模式
  • 基于大数据的热点话题分析系统的设计与实现中期
  • 从零构建AI镜像,缓存命中率提升至95%的3个核心技巧
  • 【往届已检索、ACM出版、见刊检索稳定】第二届数字管理与信息技术国际学术会议 (DMIT 2026)
  • Java 大视界 -- Java 大数据在智能家居能源管理与节能优化中的深度应用
  • 基于大数据的热点话题分析系统的设计与实开题报告 (1)
  • 腾讯云国际站代理商的QAPM服务能提供哪些专属服务?
  • FreeIPA能建立用户组,并将域组带入到加域的客户端
  • 基于java的SpringBoot/SSM+Vue+uniapp的仓储管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
  • 如何解决 pip install 网络报错 ERROR: No matching distribution found for requests
  • 【往届均已成功见刊检索、早鸟优惠】第六届计算机网络安全与软件工程国际学术会议(CNSSE 2026)
  • 200Smart与WinCC通讯
  • 软件测试资源大全:从工具到社区,打造你的职业成长生态
  • 【量子开发效率翻倍秘诀】:深度集成VS Code实现Q#与Python双向代码导航
  • 基于大数据的热点话题分析系统的设计与实开题报告
  • 面向数字孪生系统的全方位测试解决方案
  • 零基础想学黑客技术?整理国内优质网络安全论坛网站,小白入门必备!
  • 为什么90%的团队搞不定云原生Agent部署?Docker批量方案深度拆解
  • Docker Compose Agent配置实战(5个真实场景+完整代码示例)
  • 基于java的SpringBoot/SSM+Vue+uniapp的旅游管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
  • 车载 Android 系统稳定性问题全解析:从性能到黑屏的排查指南