ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux系统编程学:互斥锁与信号量

Linux系统编程学:互斥锁与信号量

线程控制

一. 互斥锁:

什么是互斥:

1.概念: 在多线程中对临界资源的排他性访问。
2.临界资源:在多线程中,会对这个资源进行读写操作的。全局变量,文件,设备。
3.排他访问:在多线程中,只能有一个线程进行读写操作的。
用途:
4.在多线程中:一个资源同一时刻,只能有一个线程


th1,th2, 是两个线程,是并发运行。也就是代码在运行是,是穿插进行的。
如果A++,这个指令翻译成汇编是3句(至少)。在一次线程的调度,这3句汇编,不一定一次性执行。也就
是说可呢个th1 执行了 1,2步,就切换到th2 执行。这个时候就会发生错误(数据的一致行性出现问题)

互斥锁的使用步骤:

定义互斥锁 ==> 初始化锁 ==>加锁 ==>解锁 ==>销毁

2、初始化锁

pthread_mutex_t mutex;

int pthread_mutex_init(pthread_mutex_tmutex,const pthread_mutexattr_tattr)

功能:将已经定义好的互斥锁初始化。
参数:mutex 要初始化的互斥锁
atrr:初始化的值,一般是NULL表示默认

与互斥锁的关系和区别:
互斥包含同步,同步是互斥的一个特例

3、加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
**功能:**用指定的互斥锁开始加锁代码
参数: mutex 用来给代码加锁的互斥锁

注意:
1.加锁后的代码到解锁部分的代码属于原子操作,
2.在加锁期间其他进程/线程都不能操作该部分代码
3.如果该函数在执行的时候,mutex已经被其他部分使用则代码阻塞。
4.原子操作: 在线程的一次调度中,这段代码必须完成,不能发生线程调度。

4、解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:将指定的互斥锁解锁。
参数:用来解锁的互斥锁

解锁之后代码不再排他访问,一般加锁解锁同时出现

5、销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:使用互斥锁完毕后需要销毁互斥锁
参数:mutex 要销毁的互斥锁

只要是多线程共享的全局变量,读和写都要加同一把锁保护

死锁

产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等

注意:

  • 加锁范围尽量小:只包住真正访问共享数据的语句,锁内不要做 IO、sleep 等耗时操作。
  • 避免死锁:多个锁时约定统一的加锁顺
  • 不要重复加锁:默认互斥锁不可重入,同一线程重复 lock 会死锁

4. 信号量:

信号量本质是一个带原子操作的计数器:

  • sem_wait:计数减 1,若为 0 则阻塞等待;
  • sem_post:计数加 1,并唤醒等待者。

POSIX 信号量有两套接口:无名信号量 <semaphore.h>(线程间常用)和有名信号量 sem_open(可跨进程,。这里用无名信号量。


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 =》申请资源=》申请一个二值信号量
V =》释放资源=》释放一个二值信号量

P操作对应函数 ==》sem_wait();
V操作对应函数 ==》sem_post();

int sem_wait(sem_t *sem);
功能:判断当前sem信号量是否有资源可用。
如果sem有资源(==1),则申请该资源,程序继续运行
如果sem没有资源(==0),则线程阻塞等待,一旦有资源种类则自动申请资源并继续运行程序。
注意:sem 申请资源后会自动执行 sem = sem - 1;
参数:sem 要判断的信号量资源

int sem_post(sem_t *sem);
功能:函数可以将指定的sem信号量资源释放
并默认执行,sem = sem+1;
线程在该函数上不会阻塞。
参数:sem 要释放资源的信号量
返回值:成功 0
失败值; -1;

4、信号量的销毁
int sem_destroy(sem_t *sem);
功能:使用完毕将指定的信号量销毁
参数:sem要销毁的信号量
返回值:成功 0
失败值; -1;

用信号量实现互斥

#include<pthread.h>#include<semaphore.h>#include<stdio.h>intcounter=0;sem_tsem;void*add(void*arg){for(inti=0;i<1000000;i++){sem_wait(&sem);counter++;sem_post(&sem);}returnNULL;}intmain(void){pthread_tt1,t2;sem_init(&sem,0,1);// 第二个参数 0 表示线程间共享,初值 1 即互斥pthread_create(&t1,NULL,add,NULL);pthread_create(&t2,NULL,add,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);sem_destroy(&sem);printf("counter = %d\n",counter);return0;}

生产者—消费者:

#include<pthread.h>#include<semaphore.h>#include<stdio.h>#defineN5intbuffer[N];intin=0,out=0;sem_tempty_sem;// 空槽位数量sem_tfull_sem;// 已填充数量pthread_mutex_tlock=PTHREAD_MUTEX_INITIALIZER;void*producer(void*arg){for(inti=0;i<10;i++){sem_wait(&empty_sem);// 等一个空位pthread_mutex_lock(&lock);buffer[in]=i;in=(in+1)%N;pthread_mutex_unlock(&lock);sem_post(&full_sem);// 通知有新数据printf("生产 %d\n",i);}returnNULL;}void*consumer(void*arg){for(inti=0;i<10;i++){sem_wait(&full_sem);// 等有数据pthread_mutex_lock(&lock);intv=buffer[out];out=(out+1)%N;pthread_mutex_unlock(&lock);sem_post(&empty_sem);// 归还空位printf("消费 %d\n",v);}returnNULL;}intmain(void){pthread_tp,c;sem_init(&empty_sem,0,N);sem_init(&full_sem,0,0);pthread_create(&p,NULL,producer,NULL);pthread_create(&c,NULL,consumer,NULL);pthread_join(p,NULL);pthread_join(c,NULL);sem_destroy(&empty_sem);sem_destroy(&full_sem);pthread_mutex_destroy(&lock);return0;}

这里的分工很清晰:信号量负责「有没有」的计数与阻塞,互斥锁负责「改缓冲区」的原子性。两者不能互相替代。

5. 三者对比与选择

原语语义典型场景
线程并发执行流并行计算、IO 并发、任务拆分
互斥锁二值、有所有权保护共享变量、临界区
信号量计数、无所有权资源池限流、生产者—消费者、事件通知

选择建议:

  • 只是保护一段共享数据 → 互斥锁,语义最直接。
  • 需要控制「同时最多 N 个」或协调生产/消费节奏 → 信号量。
  • 需要等待某个条件成立(如数据就绪)→ 条件变量 pthread_cond_t 往往比信号量更合适。

##注意:

  • 没有 pthread_join,程序提前退出导致线程没跑完,输出结果不稳定。
  • 在锁内调用 printf,虽然能跑但严重拖慢性能,后来把打印挪到锁外。
  • 信号量初值写错:empty_sem 应为缓冲区大小,full_sem应为 0,写反后消费者直接卡死。
  • 编译时漏了 -lpthread,报 undefined reference to pthread_create。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进