目录
一 前言
二 线程饥饿
三 线程同步
四 条件变量
1. cond ( condition)
2. pthread_cond_wait() :
3. pthread_cond_signal()
五 条件变量的使用
一 前言
在上篇文章Linux : 多线程互斥-CSDN博客我们讲解了线程互斥的概念,为了防止多个线程同时访问一份临界资源而出问题,我们引入了线程互斥,线程互斥其实就是多个线程同时争抢一份资源,谁抢到了就是谁的,抢不到的只能等待着下一次抢。虽然解决了多个线程同时访问同一资源所产生的问题,但是我们思考一下这样子合理吗?不合理,这会产生另一种问题——线程饥饿。
二 线程饥饿
那么线程饥饿是什么呢?为了便于理解,我们可以极端的考虑问题,假设在多线程情况下,存在着两类优先级不同的线程,一类线程的优先级非常高,另一类的线程的优先级非常低,他们开始同时争抢临界资源,假设高优先级的线程拿到了资源,上了锁之后,其他的线程只能等。直到该线程使用完临近资源后解锁,接着所有线程又开始争抢资源,而高优先级的线程因为其优先性会再一次争抢到资源,如循环往复,导那些低优先级的线程总是在等待中,永远拿不到或者很少次数拿到资源,这样被称为饥饿或者饿死。这种争抢临界资源的方式虽然是没有什么错误,但是总归来说是不合理的。
三 线程同步
在线程只使用互斥的方式去访问临界资源的时候,就可能会出现某些线程饥饿的情况。那么在操作系统中有没有一种机制,在某一时刻既可以只让一个线程去访问临界资源,但是又可以让所有的的线程按照一定的顺序访问资源呢?所有的线程就像排队一样一个个轮流访问资源,当某一线程访问玩临界资源的时候,他就去队尾等待。这样所有的线程的执行流都可以访问到资源,从而杜绝了线程饥饿的问题。 这样的机制叫做——同步,即线程同步:在保证临界资源安全的前提下,让执行流访问临界资源具有一定的顺序性。
四 条件变量
那么同步是怎么实现的呢?同步离不开一个东西——条件变量,条件变量是一种可以实现线程同步的机制,通过条件变量,可以实现让线程有序的访问临界资源。
条件变量,顾名思义它是一个执行的“条件”,当线程需要访问临界资源时,如果临界资源不满足一定的条件,那就让线程进行等待,如果满足条件,则让线程继续恢复执行的机制。它是 一个 pthread_cond_t 结构体类型的变量,并且在 pthread 库中也提供了一些条件变量相关的接口。
1. cond ( condition)
pthread_cond_t 是定义条件变量的类型。
条件变量的使用是和互斥锁差不多的。
-
条件变量的初始化可以和互斥量相同有两种,一种是调用接口 pthread_cond_init() 初始化,第一个参数是条件变量的地址,第二个参数是条件变量的属性(暂时不考虑)。需要注意的是,用该接口初始化的条件变量在不需要使用的时候,需要调用 pthread_cond_destroy() 接口来销毁掉。
- 使用宏初始化的条件变量就不用手动调用接口来销毁了。
2. pthread_cond_wait() :
条件变量等待的接口
-
这么多等待的接口中 pthread_cond_wait() 接口是最常用的,它是pthread库提供的使用条件变量等待的接口,线程调用此接口,线程就会立即进入等待。
-
pthread_cond_timedwait() 也是pthread提供给的使用条件变量等待的接口,不过看他的名字也知道它是一种定时让线程等待的接口,即可以通过该接口设置一定的时间,在此时间内让线程等待,如果此时间内,条件满足了,线程就会被自动唤醒,继续执行代码。
-
我们可以看到这两个接口的参数中都有 互斥锁 ,他们是和互斥锁一起配合使用的。
上面讲到了两个通过条件变量让线程进行等待的接口,既然有等待的接口,那么自然就存在着通过条件变量去唤醒线程的接口。如下
3. pthread_cond_signal()
调用该接口可以让某个通过指定条件变量陷入等待的线程被唤醒。
五 条件变量的使用
下面我们写个测试使用一下条件变量
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
int tickets=1000;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//定义一个锁
pthread_cond_t cond =PTHREAD_COND_INITIALIZER;//定义一个条件变量
void* start_routine(void* args)
{
std::string name =static_cast<const char*>(args);
while(true)
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
std::cout<<name<<" ->"<<tickets--<<std::endl;
pthread_mutex_unlock(&mutex);
}
}
int main()
{
//通过条件变量控制线程的执行
pthread_t t1,t2;
pthread_create(&t1,nullptr,start_routine,(void*)"thread 1");
pthread_create(&t2,nullptr,start_routine,(void*)"thread 2");
while(true)
{
sleep(1);
pthread_cond_signal(&cond);//唤醒该条件下所以的线程
std::cout<<"main thread wake up one thread...."<<std::endl;
}
pthread_join(t1,nullptr);
pthread_join(t2,nullptr);
return 0;
}
测试结果
从测试结果可以看到pthread_cond_signal()对线程的唤醒是以一定顺序来进行的。注意,pthread_cond_signal()是一次对一个线程进行唤醒,我们也可以使用 pthread_cond_broadcast()来唤醒所有的在等待中的线程。
int main()
{
//通过条件变量控制线程的执行
pthread_t t1,t2;
pthread_create(&t1,nullptr,start_routine,(void*)"thread 1");
pthread_create(&t2,nullptr,start_routine,(void*)"thread 2");
while(true)
{
sleep(1);
//一次唤醒所以线程
pthread_cond_broadcast(&cond);//唤醒该条件下所以的线程
std::cout<<"main thread wake up one thread...."<<std::endl;
}
pthread_join(t1,nullptr);
pthread_join(t2,nullptr);
return 0;
}
条件变量:通过条件控制线程的执行