文章目录
- 条件变量相关函数
- 初始化条件变量-pthread_cond_init
- 销毁条件变量-pthread_cond_destroy
- 等待条件变量-pthread_cond_wait
- 唤醒等待条件变量
- pthread_cond_broadcast
- pthread_cond_signal
- 小例子
- 关于等待函数的补充
- 条件变量使用规范
条件变量相关函数
初始化条件变量-pthread_cond_init
初始化条件变量的函数叫做pthread_cond_init
#include<pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参数说明
cond:需要初始化的条件变量
attr:初始化条件变量的属性,一般设置为NULL
返回值说明
初始化成功返回0,失败返回错误码
注意:调用pthread_cond_init
函数初始化条件变量叫做动态分配,我们还可以用静态分配的方式初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
销毁条件变量-pthread_cond_destroy
销毁条件变量的函数叫做pthread_cond_destroy
#include<pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
参数说明
cond:需要销毁的条件变量
返回值说明
销毁成功返回0,失败返回错误码
注意:使用PTHREAD_COND_INITIALIZER
初始化 (静态分配)的条件变量不需要销毁
等待条件变量-pthread_cond_wait
等待条件变量满足的函数叫做pthread_cond_wait
#include<pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
参数说明
cond:需要等待的条件变量
mutex:当前线程所处临界区对应的互斥锁
返回值说明
函数调用成功返回0,失败返回错误码
唤醒等待条件变量
唤醒等待的函数有以下两个
pthread_cond_broadcast
#include<pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal
#include<pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
参数说明
cond:唤醒在cond条件变量下等待的线程
返回值说明
函数调用成功返回0,失败返回错误码
区别
- pthread_cond_signal函数用于唤醒等待队列中首个线程
- pthread_cond_broadcast函数用于唤醒等待队列中的全部线程
小例子
下面我们用主线程创建三个新线程,让主线程控制这三个新线程, 这三个新线程创建后都在条件变量下进行等待,直到被主线程唤醒
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mtx;//互斥锁
pthread_cond_t cond;//条件变量
//主线程控制新线程
void *ctrl(void *args)
{
std::string name = (char*)args;
while(1)
{
std::cout << "master say : begin work" << std::endl;
//pthread_cond_signal函数作用:唤醒在条件变量下等待的 一个 线程
//哪一个呢?当前在等待队列里等待的第一个线程
pthread_cond_signal(&cond);
sleep(2);
}
}
void *work(void *args)
{
int number = *(int*)args;
delete (int*)args;
while(1)
{
pthread_cond_wait(&cond, &mtx);//等待条件变量
std::cout << "worker: " << number << " is working ..." << std::endl;
}
}
#define NUM 3
int main()
{
pthread_mutex_init(&mtx, nullptr);//初始化这把锁
pthread_cond_init(&cond, nullptr);//初始化条件变量
pthread_t master;
pthread_t worker[NUM];
pthread_create(&master, nullptr, ctrl, (void*)"boss");
//创建NUM个线程
for(int i = 0; i < NUM; i++)
{
int *number = new int(i);
pthread_create(worker+i, nullptr, work, (void*)number);
}
//线程等待
for(int i = 0; i < NUM; i++)
{
pthread_join(worker[i], nullptr);
}
pthread_join(master, nullptr);//线程等待
pthread_mutex_destroy(&mtx);//释放锁
pthread_cond_destroy(&cond);//释放条件变量
return 0;
}
此时我们会发现唤醒这三个线程时具有明显的顺序性
根本原因是当这若干个线程启动时默认都会在该条件变量下去等待,而我们每次都唤醒的是在当前条件变量下等待的头部线程,当该线程执行完打印操作后会继续排到等待队列的尾部进行wait,所以我们能够看到一个周转的现象
如果我们想每次唤醒都将在该条件变量下等待的所有线程进行唤醒,可以将代码中的pthread_cond_signal
函数改为pthread_cond_broadcast
函数
//主线程控制新线程
void *ctrl(void *args)
{
std::string name = (char*)args;
while(1)
{
std::cout << "master say : begin work" << std::endl;
pthread_cond_broadcast(&cond);
sleep(2);
}
}
此时我们每一次唤醒都会将所有在该条件变量下等待的线程进行唤醒==>也就是每次都将这三个线程唤醒
关于等待函数的补充
为什么
pthread_cond_wait
函数的第二个参数需要传入互斥量
1)条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程
2)条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化,所以一定要用互斥锁来保护,没有互斥锁就无法安全的获取和修改共享数据
3)当线程进入临界区时需要先加锁,然后判断内部资源的情况,若不满足当前线程的执行条件,则需要在该条件变量下进行等待,但此时该线程是拿着锁被挂起的,也就意味着这个锁再也不会被释放了,此时就会发生死锁问题
4)所以在调用pthread_cond_wait
函数时,还需要将对应的互斥锁传入,此时当线程因为某些条件不满足需要在该条件变量下进行等待时,就会自动释放该互斥锁
5)当该线程被唤醒时,该线程会接着执行临界区内的代码,此时便要求该线程必须立马获得对应的互斥锁,因此当某一个线程被唤醒时,实际会自动获得对应的互斥锁
总结:
- 当该线程进入等待的时候,互斥锁会自动释放,而当该线程被唤醒时,又会自动获得对应的互斥锁
- 条件变量需要配合互斥锁使用,其中条件变量是用来完成同步的,而互斥锁是用来完成互斥的
pthread_cond_wait
函数有两个功能,一就是让线程在特定的条件变量下等待,二就是让线程释放对应的互斥锁
我们可以不可以:当我们进入临界区上锁后,如果发现条件不满足,先解锁,然后在该条件变量下进行等待
即:
//错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false){
pthread_mutex_unlock(&mutex);//解锁
//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
pthread_cond_wait(&cond);
pthread_mutex_lock(&mutex);//再加锁
}
pthread_mutex_unlock(&mutex);//解锁
不可行!因为解锁和等待不是原子操作,调用解锁之后,在调用pthread_cond_wait
函数之前,如果已经有其他线程获取到互斥量,发现此时条件满足,于是发送了信号,那么此时pthread_cond_wait
函数将错过这个信号,最终可能会导致线程永远不会被唤醒,因此解锁和等待必须是一个原子操作
进入pthread_cond_wait
函数后,会先判断条件变量是否等于0,若等于0则说明不满足,此时会先将对应的互斥锁解锁,直到pthread_cond_wait
函数返回时再将条件变量改为1,并将对应的互斥锁加锁
条件变量使用规范
等待条件变量的代码
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);//解锁