ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

第61天 线程的互斥和同步

第61天 线程的互斥和同步

一.互斥锁

互斥锁(Mutex,即Mutual Exclusion Lock)是并发编程中用于保护 “共享资源”的同步工具,

本质是一把 “独占钥匙”;

1.当多个线程同时读写共享资源时,会出现 “竞态条件”,导致数据错误;

2.互斥锁能强制线程 “排队访问” 资源,避免竞争;

核心作用:保证 “同一时刻,只有一个线程能持有锁并访问共享资源”,实现 “互斥访问”;

#include <pthread.h>

1.pthread_mutex_init函数

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

动态初始化互斥锁

mutex 指定要进行初始化的互斥锁变量

attr 设置互斥锁的属性,若传NULL使用系统默认属性

pthread_mutex_t = PTHREAD_MUTEX_INITIALIZER; --- 静态初始化互斥锁

成功:返回0 失败:返回非0错误码

2.pthread_mutex_lock函数

int pthread_mutex_lock(pthread_mutex_t *mutex);

用于获取互斥锁;

当互斥锁已被其他线程获取时,调用该函数的线程会被阻塞,直到互斥锁可用并成功获取;

mutex 互斥锁

成功:返回0 失败:返回非0错误码

3.pthread_mutex_trylock函数

int pthread_mutex_trylock(pthread_mutex_t *mutex);

用于获取互斥锁;

当互斥锁已被其他线程获取时,调用该函数的线程不会被阻塞,而是立即返回错误;

mutex 互斥锁

成功:返回0 失败:返回非0错误码

4.pthread_mutex_unlock函数

int pthread_mutex_unlock(pthread_mutex_t *mutex);

解锁

一般使用时,只有加锁的线程可以解锁

mutex 互斥锁

返回值 成功:返回0 失败:返回非0错误码

5.pthread_mutex_destroy函数

int pthread_mutex_destroy(pthread_mutex_t *mutex);

销毁互斥锁

mutex 互斥锁

返回值 成功:返回0 失败:返回非0错误码

二.条件变量(cond)

条件变量是并发编程中用于实现线程间“等待-通知”机制的同步工具,

可让线程在某个条件不满足时挂起, 待条件满足后被唤醒;

1.仅用互斥锁能保证共享资源访问的互斥性,但无法解决 “线程需等待特定条件成立才能继续执行” 的 场景(比如生产者 - 消费者模型中,消费者需等待生产者生产出数据才能消费);

2.条件变量可结合互斥锁,让线程在条件不满足时主动释放锁并等待,条件满足时被唤醒以重新获取锁继续执行,灵活应对依赖条件的同步需求;

核心作用:实现线程间的 “条件等待与通知”,使线程能在特定条件下协作,避免线程忙等情况消耗 CPU 资源,提升并发效率;

1.pthread_cond_init函数

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

动态初始化条件变量

cond 用于指定要初始化的条件变量

attr 设置条件变量的属性,若为NULL则使用系统默认属性

pthread_cond_t cond = PTHREAD_COND_INITIALIZER; --- 静态初始化条件变量

成功:返回0 失败:返回非0错误码

2.pthread_cond_signal函数

int pthread_cond_signal(pthread_cond_t *cond);

唤醒至少一个等待在指定条件变量(cond)上的线程

cond 指定要操作的条件变量

成功:返回0 失败:返回非0错误码

3.pthread_cond_broadcast函数

int pthread_cond_broadcast(pthread_cond_t *cond);

唤醒所有等待在指定条件变量(cond)上的线程

cond 指定要操作的条件变量

成功:返回0 失败:返回非0错误码

4.pthread_cond_wait函数

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

线程阻塞在条件变量cond上,等待被唤醒;

同时,该函数会自动释放传入的互斥锁mutex,当线程被唤醒时,又会自动重新获取该互斥锁;

保证了线程在等待条件过程中互斥锁的正确管道,避免死锁等问题;

cond 用于指定线程等待的条件变量

mutex 与条件变量配合使用,保证线程在等待条件时的互斥

成功:返回0 失败:返回非0错误码

5.pthread_cond_destroy函数

int pthread_cond_destroy(pthread_cond_t *cond);

销毁条件变量相关的资源

cond 要销毁的条件变量

成功:返回0 失败:返回非0错误码

三.无名信号量

无名信号量是一种用于线程同步或进程同步的工具,通过维护一个计数值来控制对共享资源的访问,可限制同时访问资源的线程或进程数量;

1. 互斥锁只能保证同一时刻一个线程访问资源,而有些场景需要允许多个线程同时访问;

2. 无名信号量的计数值可灵活设置,能实现 “多线程有限并发访问” 或 “线程间的计数同步”,补充互斥锁在多资源并发场景的不足;

核心作用:

通过计数值控制共享资源的并发访问数量,

支持多线程在一定限制下同时访问资源,或实现线程间基于计数的同步协作;

#include <semaphore.h>

1.sem_init函数

int sem_init(sem_t *sem,int pshared,unsigned int value);

初始化无名信号量

sem 要初始化的信号量

pshared:

pshared = 0:信号量用于进程内的线程间同步(多个线程共享进程内存,信号量仅在当前进程内有效)

pshared != 0:信号量用于进程间同步(需将信号量放在共享内存中,被多个进程映射后共同访问)

value: 信号量的初始计数,表示可用资源的初始数量

value = 1:初始时,有 1 个 “资源” 可用,可用于实现互斥(一次一个线程/进程获取)

value = 0:初始时,没有“资源”可用,调用 sem_wait 的线程/进程会阻塞,直到sem_post增加计数

成功:返回0 失败:返回-1,重置错误码

2.sem_wait函数(P操作)

int sem_wait(sem_t *sem);

信号量的值减1

如果信号量的当前值大于 0,减 1 后函数直接返回(表示“获取资源成功”)

如果信号量的当前值等于 0,sem_wait 会阻塞,直到有其他线程/进程调用sem_post函数,使得信号量值大于0后, 再执行“减 1”并返回

sem 无名信号量

成功:返回0 失败:返回-1,重置错误码(不会改变信号量的值)

3.sem_post函数(V操作)

int sem_post(sem_t *sem);

释放信号量(将信号量的值+1)

sem 用于指定对哪个信号量进行释放操作

成功:返回0 失败:返回-1,重置错误码(不会改变信号量的值)

4.sem_destroy函数

int sem_destroy(sem_t *sem);

销毁无名信号量,释放该信号量相关的资源

sem 指定对哪个信号量进行销毁操作

成功:返回0 失败:返回-1,重置错误码

sem_getvalue (获取信号量当前值)

四.线程的互斥与同步应用场景

一.互斥锁

1.应用场景特点

单一共享资源,同一时刻只允许一个线程访问;主要用于防止数据竞争和不一致性问题

2.优势

实现简单,易于理解和使用; 能有效解决单一资源的互斥访问问题, 保障数据的一致性和完整性

3.局限性

如果线程长时间持有锁,可能导致其他线程长时间等待,降低系统并发性能;

若使用不当,容易产生死锁问题,比如多个线程循环等待对方持有的锁

二.条件变量

1.应用场景特点

线程需要在某个条件满足时才能继续执行;配合互斥锁,实现线程间的等待-通知机制

2.优势

避免线程的忙等待,有效节省CPU资源, 提高程序的运行效率;

可以实现更灵活的线程同步逻辑,满足 复杂的条件等待和协作需求。

3.局限性

必须与互斥锁配合使用,条件的判断和通知的时机需要准确把握,

否则可能导致线程无法正确唤醒或错过唤醒,影响程序的正确性

三.无名信号量

1.应用场景特点

限制对共享资源的并发访问数量; 可用于线程或进程的同步和计数控制

2.优势

可以灵活控制并发访问的资源数量, 适用于需要对资源进行定量管理的场景;

不仅能实现互斥,还能进行计数同步, 功能比互斥锁更丰富

3.局限性

信号量的计数管理需要准确设置和维护, 容易出现资源过度使用或浪费的情况;

RELATED READING

延伸阅读

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