文章目录
latex
多线程共享全局变量 → 数据竞争问题
↓
需要同步机制 → 互斥量(Mutex)解决方案
↓
互斥量使用模式 → 初始化、加锁、解锁、销毁
全局变量同步问题
- 在多线程程序中,如果多个线程同时对共享的全局变量进行读写操作,可能会出现数据竞争(Race Condition)问题,导致最终结果与预期不一致
c
/* 为突出问题 代码省略了错误处理*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
static int glob; // 全局变量,被所有线程共享
// 线程函数
void *ThreadFunc(void *arg) {
int loops = *((int *)arg); // 从参数中获取循环次数
int loc, j;
for(j = 0; j < loops; j++) {
loc = glob; // 读取全局变量
loc++; // 局部变量自增
glob = loc; // 将局部变量的值赋给全局变量
}
pthread_exit(NULL); // 线程退出
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2; // 定义两个线程 ID
int loops;
// 检查命令行参数
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);
return 0;
}
- 理论上,如果
loops为 n,最终glob的值应该是 2n,但结果似乎并不总是这样

- 两个线程同时执行上述操作时,可能会出现以下情况:
- 线程A读取glob值(例如100)
- 线程B也读取glob(仍为100)
- 两个线程都执行loc++
- 最终glob可能只增加一次,而非两次
- 被称为非原子操作导致的同步问题
互斥量
- 互斥量(Mutex)是一种锁机制,用于保护共享资源,确保同一时间只有一个线程能访问该资源
- 属于pthread_mutex_t类型,需在使用前初始化
- 合理使用互斥量可保证线程安全,提升程序稳定性
互斥量初始化方式
静态初始化(编译时)
- 对于静态分配的互斥量而言,将
PTHREAD_MUTEX_INITIALIZER赋给互斥量 - 适用于全局变量或静态变量
- 系统会自动初始化为解锁状态
c
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
静态初始化的方式等效于通过调用
pthread_mutex_init()进行动态初始化,并将参数attr指定为NULL,但不会执行错误检查
动态初始化(运行时)
- 适用于动态分配于堆或栈上的互斥量
- 动态创建针对某一结构的链表,表中每个结构都包含一个
pthread_mutex_t类型的字段来存放互斥量,借以保护对该结构的访问
- 动态创建针对某一结构的链表,表中每个结构都包含一个
- 需要自定义属性
c
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_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)
c
pthread_mutex_lock(&mutex);
// 临界区:访问共享资源的代码
glob++;
pthread_mutex_unlock(&mutex);
互斥量解决同步问题
- 静态初始化
c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int glob; //临界资源
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化互斥量
void *ThreadFunc(void *arg) {
int loops = *((int *)arg);
int loc, j;
for(j = 0; j < loops; j++) {
pthread_mutex_lock(&mutex);
//加锁与解锁的代码区域被称为 临界区
loc = glob;
loc++;
glob = loc;
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2;
int loops;
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);
}
int ret = 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);
return 0;
}
- 动态初始化
c
/* 省略了错误处理*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
static int glob;
struct thread {
int loops;
pthread_mutex_t mutex;
};
// 线程函数
void *ThreadFunc(void *arg) {
struct thread *data = (struct thread *)arg; // 从参数中获取循环次数
int loc, j;
for(j = 0; j < data->loops; j++) {
pthread_mutex_lock(&data->mutex);
glob++;
pthread_mutex_unlock(&data->mutex);
}
pthread_exit(NULL); // 线程退出
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2; // 定义两个线程 ID
struct thread data;
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);
return 0;
}
注意问题
- 静态初始化用于全局/静态变量,动态初始化用于局部/动态变量
- 不要重复初始化已初始化的互斥量
- 不要在锁定的状态下销毁互斥量
- 确保所有线程解锁后再销毁互斥量
- 加锁与解锁必须成对出现,避免死锁
- 临界区应尽量简短,避免长时间占用锁
- **互斥锁无论读写都只能被一个线程持有。****读写锁的核心特点是读锁共享(多个线程可同时持有读锁)、写锁独占(仅一个线程可持有写锁)。**两者都需要初始化,性能取决于场景(读多写少场景读写锁更优)