文章目录
- 引言
- 条件变量
- 初始化条件变量:pthread_cond_init
- 销毁条件变量:pthread_cond_destroy
- 条件等待:pthread_cond_wait
- 唤醒等待:pthread_cond_signal、pthread_cond_broadcast
- 认识条件变量
- 接口使用
引言
有一个非常好的VIP自习室,一次只允许一个人进来,每一个自习完成的同学归还钥匙后,不能立马申请,第二次申请必须排队,也就是说其他人也必须排队,这种去自习室自习有一定的顺序性,这称之为同步。
换言之,每一个线程在访问临界资源时,有一定的顺序性,这称之为线程的同步。这里的顺序性可以是严格的顺序性,也可以是宏观上的具有相对顺序性。
条件变量
一个条件变量是一个pthread_cond_t
类型
初始化条件变量:pthread_cond_init
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
cond
:要初始化的条件变量
attr
:NULL
销毁条件变量:pthread_cond_destroy
int pthread_cond_destroy(pthread_cond_t *cond)
在调用 pthread_cond_destroy
之前,确保没有线程在等待这个条件变量
条件等待:pthread_cond_wait
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
cond
:要在这个条件变量上等待
mutex
:互斥量
唤醒等待:pthread_cond_signal、pthread_cond_broadcast
唤醒一个线程:
int pthread_cond_signal(pthread_cond_t *cond);
唤醒所有线程:
int pthread_cond_broadcast(pthread_cond_t *cond);
认识条件变量
有两个人分别是A和B,B往箱子中放苹果,A从箱子中拿苹果,A必须在B放完后拿苹果。箱子实际上是一个共享资源,苹果相当于数据,当B没有往共享资源中写数据时,A进行读取,那么读取的内容就是垃圾数据。A,B在进行操作的时候,必须保证箱子(共享资源)的安全,因此需要加锁pthread_mutex_lock
,完成之后进行解锁pthread_mutex_unlock
。注定A,B需要互斥式的访问箱子(共享资源),这样才能保证B正在写的时候,A不会读取。A需要先检测箱子(共享资源)重是否有苹果(数据),当检测到没有苹果(数据),A需要解锁退出,然后A立马重新申请锁,再去检测,因此A可能在不断申请锁、解开锁。A如此频繁的申请锁,B可能抢不到锁。因此这种做法不合理:A、B互斥,如果B竞争能力弱的话,B抢不到锁,A只能频繁申请锁、解开锁。
为了解决这个矛盾,在上述场景中,引入一个铃铛,当A检测到箱子(共享资源)中没有苹果(数据),A解开锁之后不要再去申请锁,而是去铃铛那里等待铃铛,B放完苹果后摇铃铛,听到铃铛响了,A再去申请锁。
这里引入的铃铛就是条件变量,条件变量必须提供两个东西:
- 需要一个线程队列
- 需要有通知机制
此时又来一个C,也是来拿苹果,A和C就会形成竞争了,铃铛想起的时候,就会把A和C都唤醒,这就是pthread_cond_broadcast
接口使用
#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
const int num=5;
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t gcond=PTHREAD_COND_INITIALIZER;
void *Wait(void *args)
{
std::string name=static_cast<const char*>(args);
while (true)
{
pthread_mutex_lock(&gmutex);
//条件等待
pthread_cond_wait(&gcond,&gmutex);
usleep(10000);
std::cout<<"I am : "<<name<<std::endl;
pthread_mutex_unlock(&gmutex);
//usleep(100000);
}
}
int main()
{
pthread_t threads[num];
for(int i=0;i<num;i++)
{
char *name=new char[1024];
snprintf(name,1024,"thread-%d",i+1);
pthread_create(threads+i,nullptr,Wait,(void *)name);
}
//唤醒线程
while(true)
{
pthread_cond_signal(&gcond); //一次唤醒一个线程
//pthread_cond_broadcast(&gcond); //一次唤醒多个线程
std::cout<<"唤醒一个线程..."<<std::endl;
sleep(2);
}
for(int i=0;i<num;i++)
{
pthread_join(threads[i],nullptr);
}
return 0;
}
为什么pthread_cond_wait
在加锁和解锁之间使用?
确保条件检查的原子性:在多线程环境中,条件变量通常与互斥锁一起使用来保护共享资源。线程在检查条件之前需要持有锁,以避免其他线程修改共享资源。调用 pthread_cond_wait 时,函数会释放锁以让其他线程可以修改共享资源,然后在条件满足后重新获取锁,这样可以保证在条件变量被触发后,线程能够再次安全地检查条件和访问共享资源。
避免竞争条件:如果 pthread_cond_wait 不释放锁,那么其他线程将无法获取这个锁并修改条件,这可能导致死锁或线程无法继续工作。通过在 pthread_cond_wait 内部释放和重新获取锁,确保了条件检查的完整性和线程的正确同步。