前言
线程同步意味着在多线程并发执行中,协调线程之间的执行顺序,以确保共享资源被正确访问和修改。线程同步的维护本质就是在安排线程之间的执行顺序。那么在linux中是如何维护线程同步的呢?本篇文章将围绕这个为题展开叙述。
线程同步的基本概念
下面介绍一些有关线程同步的基本概念。
条件变量
当一个线程互斥的访问某个变量,即访问临界资源时给临界区上互斥锁,这个时候其它线程只能等待。那什么时候其它线程可以继续申请临界资源呢?我们希望当一个线程使用完临界资源后,正在等待的线程能够知道这一事件的发生从而重新申请资源,而不是一直重复申请这个动作。
就像一个闹钟,当闹钟响了之后我们就知道该起床了,而不是睡一下又看下时间。
条件变量提供一种线程通信的方法,使得一个线程可以等待另一个线程满足某种条件后再继续执行。具体的,我们将这种通知一个线程继续执行的动作称为唤醒。
于是,借助条件变量,我们就能实现协调线程之间访问临时资源的顺序性。
同时线程库给我们提供了一些接口用来操作条件变量,下面介绍一些常见的关于条件变量的操作。(头文件都是pthread.h
)
定义条件变量
初始化条件变量
- 动态初始化
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;
销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
销毁某个条件变量,成功返回0,失败返回错误代码
等待条件(重要)
如果当前线程没有申请到临界资源,该线程可以选择等待。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
在调用该函数时,互斥锁mutex
必须被锁住,并等待唤醒。唤醒之后线程重新锁住互斥锁并继续执行。值得注意的是,pthread_cond_wait
函数首先会解锁与之关联的互斥锁mutex
,这也是为什么使用该函数时mutex
必须是被锁住的。然后调用该函数的线程进入阻塞状态,直到被唤醒。最后,条件变量被通知之后,pthread_cond_wait
重新锁定互斥锁mutex
。
对于该函数提出以下问题:
- 为什么要在
pthread_cond_wait
中传入互斥锁?- 在调用 pthread_cond_wait 时,互斥锁是已经锁住的,确保没有其他线程可以修改共享资源。
- pthread_cond_wait 在进入等待状态之前会自动释放互斥锁,使得其他线程可以修改条件。
- 当线程被唤醒后,pthread_cond_wait 会重新获得互斥锁,然后再继续执行,因为此时还在临界区,还会访问临界资源。
唤醒等待
- 唤醒某个线程
int pthread_cond_signal(pthread_cond_t *cond);
唤醒一个等待在条件变量 cond 上的线程。如果有多个线程在等待条件变量,具体唤醒哪一个线程是不确定的。
成功返回0,失败则返回错误码
- 唤醒所有正在等待该条件变量的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
唤醒所有等待在条件变量 cond 上的线程。同样成功返回0,失败则返回错误码。
简单运用
下面我们使用条件变量和互斥锁来设计一个简单的代码样例
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
pthread_cond_t cond;
pthread_mutex_t mutex;
void *r1(void *arg) // 等待函数,执行该函数的线程一直处于while (true)
{
pthread_cond_wait(&cond, &mutex);
cout << "被唤醒" << endl;
return arg;
}
void *r2(void *arg) // 唤醒函数,执行该函数的线程一直尝试唤醒某个等待的线程
{
while (true)
{
pthread_cond_signal(&cond);
cout << "唤醒某个线程" << endl;
sleep(1);
}
}
int main()
{
pthread_t t1, t2; // 定义两个线程
pthread_cond_init(&cond, NULL); // 初始化条件变量
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
pthread_create(&t1, NULL, r1, NULL); // 创建线程并分配执行函数
pthread_create(&t1, NULL, r2, NULL);
pthread_join(t1, NULL); // 等待线程退出
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex); // 销毁互斥锁和条件变量
pthread_cond_destroy(&cond);
return 0;
}
常见使用条件变量的格式
- 等待条件代码:
pthread_mutex_lock(&mutex);
while (条件为假) //不满足条件陷入等待,在循环中等待是为了防止伪唤醒
pthread_cond_wait(cond, mutex);
// ...
// 访问临界资源
//...
pthread_mutex_unlock(&mutex);
- 给条件发送信号,即可以唤醒等待条件中的线程
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond); //唤醒某个线程
pthread_mutex_unlock(&mutex);