什么是条件变量:
与互斥锁不同,条件变量是用来等待而不是用来上锁的,一个条件变量对应一个等待队列。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)唤醒线程。
条件变量的使用:
1.条件变量初始化:
(1)pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
(2)int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
参数:cond为条件变量的地址, cond_attr用来设置条件变量的属性,尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
返回值:成功返回0,失败返回错误码。
2.销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond) ;
只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。
3.等待
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
(1)条件变量必须和互斥锁一起使用, 互斥锁是用来保护“条件”的,“条件”肯定是一个共享资源,通过对“条件”状态的判断,决定是否将线程放在阻塞队列中。(例如:变量a可以是一个“条件”,规定当a等于零时,线程入阻塞队列)。但是从判断某“条件”成立,到线程入阻塞队列不是原子的,入阻塞队列后线程肯定期待别的线程通过对条件的改变,将他唤醒。这样可能出现的问题是,还没入阻塞队列,但是别的线程已经通过条件的改变,发出了唤醒信号。唤醒信号没有作用,因为阻塞队列没有线程。等时间片切换回来,再将线程入队列,就不会再被唤醒了。所以要用锁保护“条件”,在线程未真正放入阻塞队列之前不释放锁,保证条件不会被别的资源改变。当然在该线程判断条件之前要线上锁。
(2)条件变量的工作流程:
mutex.lock();
while (判断“条件”是否成立) {
pthread_cond_wait 等待
}
修改”条件“
mutext.unlock();
pthread_cond_signal 唤醒
pthread_cond_wait(cond, mutex)要完成的功能是,(1)将线程加入cond对应的等待队列。(2) 释放mutex锁。(3)当线程被唤醒后还在pthread_cond_wait()体内,然后继续加mutex加锁,获得锁资源。需要注意的是(1)和 (2)是原子操作,所以不用在意他们的先后顺序。当完成pthread_cond_wait操作后拿到锁资源后为什么还要判断“条件”,因为可能有多个线程竞争锁资源一个线程拿到锁资源后对“条件”改变,其他线程拿到做资源应该再次判断是否等待。所以需要循环判断。
唤醒
int pthread_cond_signal(pthread_cond_t *cptr);
int pthread_cond_broadcast (pthread_cond_t * cptr);
//成功返回0,失败返回错误码.
唤醒条件有两种形式,pthread_cond_signal()唤醒一个等待该条件的线程,存在多个等待线程时按入队顺序唤醒其中一个;而pthread_cond_broadcast()则唤醒所有等待线程。
生产者消费者模型实现
生产者消费者模型是指,有一块缓冲区,缓冲区有大小为 capacity。有多个生产者向缓冲区里边放数据,多个消费者从缓冲区里边取数据。
分析:(1)缓冲区满,不能放数据。(2)缓冲区空不能取数据 (3)缓冲区同时只能有一个消费者取,或者一个生产者放。
实现如下:
#include<unistd.h>
#include<iostream>
#include<queue>
#include<pthread.h>
using namespace std;
#define MAX_QUEUE 5
static int i = 0;
class BlockQueue{
public:
BlockQueue(int qmax = MAX_QUEUE)//构造函数
:_capacity(qmax)
{
pthread_mutex_init(&_mutex,NULL);
pthread_cond_init(&_cond_product, NULL);
pthread_cond_init(&_cond_consumer, NULL);
}
~BlockQueue(){//析构函数
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond_product);
pthread_cond_destroy(&_cond_consumer);
}
bool push(int date){
pthread_mutex_lock(&_mutex);
while((int)_queue.size() == _capacity){
pthread_cond_wait(&_cond_product, &_mutex);//解锁等待,唤醒后加锁
}
_queue.push(date);
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_cond_consumer);
return true;
}
bool pop(int* data){
while(_queue.size() == 0){//拿到锁资源后,判断是否满足等待条件
pthread_cond_wait(&_cond_consumer, &_mutex);//解锁等待,唤醒后加锁
}
//不满足等待条件
*data = _queue.front();//出队列
//sleep(1);
_queue.pop();
pthread_mutex_unlock(&_mutex);//操作完临界资源后解锁
pthread_cond_signal(&_cond_product);//唤醒生产者线程
return true;
}
private:
pthread_cond_t _cond_product;
pthread_cond_t _cond_consumer;
pthread_mutex_t _mutex;
queue<int> _queue;
int _capacity;
};
void* thr_product(void* grm){
BlockQueue *ptr = (BlockQueue*)grm;
while(1){
//生产者不断地写入数据
ptr ->push(i++);
printf("写入数据%d\n",i);//注意printf()和上边的push不是原子操作
}
return NULL;
}
void* thr_consumer(void* grm){
BlockQueue *ptr = (BlockQueue*)grm;
int date;
while(1){
//消费者不断地读出数据
ptr ->pop(&date);
printf("读出数据%d\n",date);
}
return NULL;
}
int main(){
BlockQueue q(3);//阻塞队列的容量为3
pthread_t pro_tid[4];//4个生产者
pthread_t con_tid[4];//4个消费者
for(int i = 0; i < 4; i++){
int res = pthread_create(&pro_tid[i],NULL, thr_product, (void*)&q);
if(res != 0){
printf("创建生产者线程失败");
return -1;
}
}
for(int i = 0; i < 4; i++){
int res = pthread_create(&con_tid[i],NULL, thr_consumer, (void*)&q);
if(res != 0){
printf("创建消费者线程失败");
return -1;
}
}
pthread_join(pro_tid[0],NULL);
pthread_join(con_tid[0],NULL);
return 0;
}