news 2026/1/23 0:59:41

线程同步之互斥量

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
线程同步之互斥量

文章目录

  • 全局变量同步问题
  • 互斥量
    • 互斥量初始化方式
      • 静态初始化(编译时)
      • 动态初始化(运行时)
    • 互斥量操作函数
    • 临界区(Critical Section)
    • 互斥量解决同步问题
      • 注意问题
多线程共享全局变量 → 数据竞争问题 ↓ 需要同步机制 → 互斥量(Mutex)解决方案 ↓ 互斥量使用模式 → 初始化、加锁、解锁、销毁

全局变量同步问题

  • 在多线程程序中,如果多个线程同时对共享的全局变量进行读写操作,可能会出现数据竞争(Race Condition)问题,导致最终结果与预期不一致
/* 为突出问题 代码省略了错误处理*/#include<pthread.h>#include<stdio.h>#include<stdlib.h>staticintglob;// 全局变量,被所有线程共享// 线程函数void*ThreadFunc(void*arg){intloops=*((int*)arg);// 从参数中获取循环次数intloc,j;for(j=0;j<loops;j++){loc=glob;// 读取全局变量loc++;// 局部变量自增glob=loc;// 将局部变量的值赋给全局变量}pthread_exit(NULL);// 线程退出}intmain(intargc,constchar*argv[]){pthread_ttid1,tid2;// 定义两个线程 IDintloops;// 检查命令行参数if(argc!=2){fprintf(stderr,"%s [loops]\n",argv[0]);exit(EXIT_FAILURE);}// 从命令行参数中读取循环次数if(sscanf(argv[1],"%d",&loops)!=1){fprintf(stderr,"Invalid loops\n");exit(EXIT_FAILURE);}// 创建两个线程,都执行 ThreadFunc 函数pthread_create(&tid1,NULL,ThreadFunc,&loops);pthread_create(&tid2,NULL,ThreadFunc,&loops);// 等待两个线程结束pthread_join(tid1,NULL);pthread_join(tid2,NULL);// 打印最终的全局变量值printf("glob = %d\n",glob);return0;}
  • 理论上,如果loops为 n,最终glob的值应该是 2n,但结果似乎并不总是这样

  • 两个线程同时执行上述操作时,可能会出现以下情况:
    • 线程A读取glob值(例如100)
    • 线程B也读取glob(仍为100)
    • 两个线程都执行loc++
    • 最终glob可能只增加一次,而非两次
  • 被称为非原子操作导致的同步问题

互斥量

  • 互斥量(Mutex)是一种锁机制,用于保护共享资源,确保同一时间只有一个线程能访问该资源
  • 属于pthread_mutex_t类型,需在使用前初始化
  • 合理使用互斥量可保证线程安全,提升程序稳定性

互斥量初始化方式

静态初始化(编译时)

  • 对于静态分配的互斥量而言,将PTHREAD_MUTEX_INITIALIZER赋给互斥量
  • 适用于全局变量或静态变量
  • 系统会自动初始化为解锁状态
#include<pthread.h>pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;

静态初始化的方式等效于通过调用pthread_mutex_init()进行动态初始化,并将参数attr指定为NULL,但不会执行错误检查

动态初始化(运行时)

  • 适用于动态分配于堆或栈上的互斥量
    • 动态创建针对某一结构的链表,表中每个结构都包含一个pthread_mutex_t类型的字段来存放互斥量,借以保护对该结构的访问
  • 需要自定义属性
#include<pthread.h>intpthread_mutex_init(pthread_mutex_t*restrict mutex,constpthread_mutexattr_t*restrict attr);
  • mutex:指定函数执行初始化操作的目标互斥量
  • attr:是指向pthread_mutexattr_t类型对象的指针,该对象在函数调用之前已经过了初始化处理,用于定义互斥量的属性。
    • 若将attr参数置为NULL,则该互斥量的各种属性会取默认值

互斥量操作函数

函数说明
pthread_mutex_lock(&mutex)加锁,若已被锁则阻塞
pthread_mutex_unlock(&mutex)解锁
pthread_mutex_trylock(&mutex)尝试加锁,失败立即返回
pthread_mutex_destroy(&mutex)销毁互斥量

临界区(Critical Section)

pthread_mutex_lock(&mutex);// 临界区:访问共享资源的代码glob++;pthread_mutex_unlock(&mutex);

互斥量解决同步问题

  • 静态初始化
#include<pthread.h>#include<stdio.h>#include<stdlib.h>#include<string.h>staticintglob;//临界资源pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;// 静态初始化互斥量void*ThreadFunc(void*arg){intloops=*((int*)arg);intloc,j;for(j=0;j<loops;j++){pthread_mutex_lock(&mutex);//加锁与解锁的代码区域被称为 临界区loc=glob;loc++;glob=loc;pthread_mutex_unlock(&mutex);}pthread_exit(NULL);}intmain(intargc,constchar*argv[]){pthread_ttid1,tid2;intloops;if(argc!=2){fprintf(stderr,"%s [loops]\n",argv[0]);exit(EXIT_FAILURE);}if(sscanf(argv[1],"%d",&loops)!=1){fprintf(stderr,"Invalid loops\n");exit(EXIT_FAILURE);}intret=pthread_create(&tid1,NULL,ThreadFunc,&loops);if(ret!=0){fprintf(stderr,"pthread_create:%s\n",strerror(ret));exit(EXIT_FAILURE);}ret=pthread_create(&tid2,NULL,ThreadFunc,&loops);if(ret!=0){fprintf(stderr,"pthread_create:%s\n",strerror(ret));exit(EXIT_FAILURE);}ret=pthread_join(tid1,NULL);if(ret!=0){fprintf(stderr,"pthread_join tid1:%s\n",strerror(ret));exit(EXIT_FAILURE);}ret=pthread_join(tid2,NULL);if(ret!=0){fprintf(stderr,"pthread_join tid2:%s\n",strerror(ret));exit(EXIT_FAILURE);}printf("glob = %d\n",glob);return0;}
  • 动态初始化
/* 省略了错误处理*/#include<pthread.h>#include<stdio.h>#include<stdlib.h>staticintglob;structthread{intloops;pthread_mutex_tmutex;};// 线程函数void*ThreadFunc(void*arg){structthread*data=(structthread*)arg;// 从参数中获取循环次数intloc,j;for(j=0;j<data->loops;j++){pthread_mutex_lock(&data->mutex);glob++;pthread_mutex_unlock(&data->mutex);}pthread_exit(NULL);// 线程退出}intmain(intargc,constchar*argv[]){pthread_ttid1,tid2;// 定义两个线程 IDstructthreaddata;pthread_mutex_init(&data.mutex,NULL);// 检查命令行参数if(argc!=2){fprintf(stderr,"%s [loops]\n",argv[0]);exit(EXIT_FAILURE);}// 从命令行参数中读取循环次数if(sscanf(argv[1],"%d",&data.loops)!=1){fprintf(stderr,"Invalid loops\n");exit(EXIT_FAILURE);}// 创建两个线程,都执行 ThreadFunc 函数pthread_create(&tid1,NULL,ThreadFunc,&data);pthread_create(&tid2,NULL,ThreadFunc,&data);// 等待两个线程结束pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_mutex_destroy(&data.mutex);// 打印最终的全局变量值printf("glob = %d\n",glob);return0;}

注意问题

  • 静态初始化用于全局/静态变量,动态初始化用于局部/动态变量
  • 不要重复初始化已初始化的互斥量
  • 不要在锁定的状态下销毁互斥量
  • 确保所有线程解锁后再销毁互斥量
  • 加锁与解锁必须成对出现,避免死锁
  • 临界区应尽量简短,避免长时间占用锁
  • **互斥锁无论读写都只能被一个线程持有。****读写锁的核心特点是读锁共享(多个线程可同时持有读锁)、写锁独占(仅一个线程可持有写锁)。**两者都需要初始化,性能取决于场景(读多写少场景读写锁更优)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/23 3:36:25

企业CI/CD中处理Git认证错误的实战指南

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个演示项目&#xff0c;模拟CI/CD管道中出现的Git认证错误场景。包含&#xff1a;1. 故意配置错误的Git凭据&#xff1b;2. 展示日志中REMOTE: INVALID USERNAME OR TOKEN错…

作者头像 李华
网站建设 2026/1/22 9:52:08

2026年,全网亲测有效的10款降ai神器盘点!(持续更新)

最近好多同学在后台问我&#xff0c;论文查重红了一片怎么办。其实呢&#xff0c;今年学校查得严&#xff0c;不仅查复制比&#xff0c;还要查AIGC。说白了&#xff0c;就是看你有没有用AI写。我自己试了一圈&#xff0c;发现降低ai率真是一门玄学。 有些同学为了免费降低ai率&…

作者头像 李华
网站建设 2026/1/22 8:04:25

零基础教程:5分钟用快马创建你的第一个DOWNKYI下载器

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个最简单的DOWNKYI单视频下载器GUI应用&#xff0c;要求&#xff1a;1) 使用PySimpleGUI构建界面 2) 输入B站视频URL即可下载 3) 提供清晰的状态提示 4) 适合完全不懂编程的…

作者头像 李华
网站建设 2026/1/18 19:36:33

【计算机毕业设计案例】基于python深度学习的乐器识别卷神经网络

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/1/22 8:04:26

1小时搭建Redis面试Demo:6大考点可视化展示

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 构建一个Redis知识可视化演示系统原型&#xff0c;要求&#xff1a;1.6个独立模块分别展示数据结构、持久化等核心概念2.实时数据流动动画&#xff08;如RDB快照过程&#xff09;3…

作者头像 李华