实用指南:学习笔记——线程控制 - 互斥与同步
线程控制 - 互斥与同步
一、 互斥(Mutex)
1.概念
在多线程中对临界资源的排他性访问。
临界资源:在多线程中会被多个线程进行读写操作的资源(全局变量、文件、设备等)
排他访问:同一时刻只能有一个线程进行读写操作
2.用途
在多线程中,一个资源同一时刻只能有一个线程访问。
示例问题
int A = 0; // 临界资源
// 线程1
void* th1(void* arg) {
A++; // 这不是原子操作!
}
// 线程2
void* th2(void* arg) {
A++;
}
A++在汇编中至少需要3步:
读取A到寄存器
寄存器值加1
将结果写回A
如果th1执行了1、2步后切换到th2,就会发生数据一致性问题。
解决方案:使用互斥锁
使用步骤
定义互斥锁
初始化锁
加锁
解锁
销毁
相关函数
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
#include
#include
#include
#include
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)
与互斥锁的区别
加锁/解锁主体:
互斥锁:加锁和解锁必须是同一个线程
信号量:可以由不同线程交叉释放(th1释放th2,th2释放th1)
使用场景:
互斥锁:临界区代码要短小精悍,不要有休眠或耗时操作
信号量:可以有适当的休眠和小耗时操作
计数信号量
信号量初值可以大于1,用于多个资源的情况。
使用步骤
定义信号量
初始化信号量
PV操作
销毁信号量
相关函数
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
#include
#include
#include
#include
#include
#include
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)
概念
由于锁资源安排不合理,导致进程/线程无法继续执行的现象。
产生死锁的四个必要条件
互斥条件:一个资源每次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
死锁示例
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时线程阻塞 |
| 计数 | 无计数功能 | 有计数功能 |
| 性能 | 轻量级,适用于短临界区 | 稍重,适用于较复杂同步 |
使用原则
能用互斥锁就用互斥锁,因为它更简单高效
临界区要短小,不要包含耗时操作
避免死锁:按固定顺序申请锁,或使用超时机制
锁的粒度要合适:不要过大(影响并发)或过小(增加开销)
浙公网安备 33010602011771号