文章目录
- 全局变量同步问题
- 互斥量
- 互斥量初始化方式
- 静态初始化(编译时)
- 动态初始化(运行时)
- 互斥量操作函数
- 临界区(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;}注意问题
- 静态初始化用于全局/静态变量,动态初始化用于局部/动态变量
- 不要重复初始化已初始化的互斥量
- 不要在锁定的状态下销毁互斥量
- 确保所有线程解锁后再销毁互斥量
- 加锁与解锁必须成对出现,避免死锁
- 临界区应尽量简短,避免长时间占用锁
- **互斥锁无论读写都只能被一个线程持有。****读写锁的核心特点是读锁共享(多个线程可同时持有读锁)、写锁独占(仅一个线程可持有写锁)。**两者都需要初始化,性能取决于场景(读多写少场景读写锁更优)