一、条件变量
条件变量是一种“事件通知机制”,它本身不提供、也不能够实现“互斥”的功能。因此,条件变量通常(也必须)配合互斥量来一起使用,其中互斥量实现对“共享数据”的互斥(即同步),而条件变量则去执行 “通知共享数据状态信息的变化”的任务。比如通知队列为空、非空,或任何其他需要由线程处理的共享数据的状态变化。实际开发中,生产者-消费者模型是经常被使用到的一个技巧,若干线程不断的往某个队列中生产数据进去,而其他若干线程不断的去队列中消费数据。这里“队列”是共享的数据,需要用互斥量来对其进行加锁,防止数据紊乱;细心的读者发现,去队列消费数据的线程怎么才能知道队列中何时有数据进来,总不能一直在那里傻傻的等待吧!如果一直等待,则需要互斥量加锁,那么生产者线程会加锁失败,会一直尝试去加锁。这无形中会导致性能的急剧下降。因此这时候是“条件变量”闪亮登场,展示它真正“技术”的时候了,它会通知对应的(或广播所有等待状态变化的)线程,告知它们这个“共享数据”的状态变化信息,以执行对应的处理。
二、条件变量API(条件变量的高级属性API没有给出)
/**@fn pthread_cond_init
* @brief 初始化条件变量cond(attr创建可选的条件变量属性)
* @param[in] pthread_cond_t *cond 条件变量
* @param[in] pthread_condattr_t *attr 条件变量高级属性
* @param[out] NONE
* @description 常见错误码:[ENOMEN]内存不足,[EAGAIN]资源不足,[EBUSY]cond已经初始化,[EINVAL]attr无效
* @return int
**/
int pthread_cond_init (pthread_cond_t *cond,
const pthread_condattr_t *attr);
/**@fn pthread_cond_signal
* @brief 信号通知条件变量cond,唤醒一个等待者
* @param[in] pthread_cond_t *cond 待唤醒的条件变量
* @param[out] NONE
* @description 常见错误码:[EINVAL] cond无效。
* @return int
**/
int pthread_cond_signal (pthread_cond_t *cond);
/**@fn pthread_cond_broadcast
* @brief 广播条件变量(唤醒所有等待该条件变量的线程,即等待者)
* @param[in] pthread_cond_t *cond 条件变量
* @param[out] NONE
* @description 常见错误码:[EINVAL] cond无效
* @return int
**/
int pthread_cond_broadcast (pthread_cond_t *cond);
/**@fn pthread_cond_timedwait
* @brief 等待条件变量被唤醒(等待条件变量cond被唤醒,直到由一个信号或广播,或绝对时间abstime到
* 才唤醒该线程)
* @param[in] pthread_cond_t *cond 条件变量
* @param[in] pthread_mutex_t *mutex 互斥量
* @param[in] const struct timespec *abstime 等待被唤醒的绝对超时时间
* @param[out] NONE
* @description 常见错误码:[EINVAL] 同时等待不同的互斥量;/cond,mutex/abstime无效;互斥量没有被主线程占有
* [ETIMEDOUT] abstime指定绝对时间超时
* @return int
**/
int pthread_cond_timedwait (pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
/**@fn pthread_cond_wait
* @brief 等待条件变量cond被唤醒(由一个信号或者广播)
* @param[in] pthread_cond_t *cond 条件变量
* @param[in] pthread_mutex_t *mutex 互斥量
* @param[out] NONE
* @description 常见错误码:[EINVAL] cond或mutex无效, [EINVAL] 同时等待不同的互斥量 [EINVAL] 主调线程没有占有互斥量
* @return int
**/
int pthread_cond_wait (pthread_cond_t *cond,
pthread_mutex_t *mutex);
/**@fn pthread_cond_destroy
* @brief 释放/销毁条件变量
* @param[in] pthread_cond_t *cond 待销毁的条件变量
* @param[out] NONE
* @description 常见错误码:[EBUSY] cond正在使用 [EINVAL]cond无效
* @return int
**/
int pthread_cond_destroy (pthread_cond_t *cond)
三、条件变量原则
(5)条件变量提供了“信号单播(注意:这里的所谓信号并非Linux下的SIGxxx)”和“信号广播”两种方式,基于更安全、高效等因素,优先考虑使用“广播信号”方式。
(6)线程发信号或广播条件变量时候看到的内存数据,同样也可以被唤醒的其他线程看到。而在发信号或广播之后写入内存的数据不会被唤醒的线程看到,即使写操作发生在线程被唤醒之前。
(7)一个内存地址一次只能保持一个值;不要让线程竞争以优先获得访问权。
(8)在等待线程醒来时候,检查其“状态”是否为真是个不错的主意;同时应该总是在一个循环中等待条件变量。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//静态初始化
int main(int argc,char **argv)
{
//其他一些操作
struct timespec strTime;
strTime.tv_sec = time(NULL)+ 3; //3s
strTime.tv_nsec = 0;
//如果"判断条件"不大于0,则等待条件变量,被唤醒
while(condition <= 0)
{
iRet = pthread_cond_timedwait(&cond,&mutex,&strTime);
if(ETIMEDOUT == iRet)
{
printf("main thread time out. iRet[%d]\n",iRet);
//return -1; //如果等待超时,则跳出循环,结束程序
continue;
}else if(0 != iRet)
{
printf("main thread timedwait err. iRet[%d]\n",iRet);
exit(0); //若等待条件变量发送信号失败,则直接结束进程
}
}
////// 程序执行到这里,说明等待线程已经等到条件变量,被唤醒,这里再次检查“其状态(也称为“谓词”)” //////
if(condition >= 0)
{
printf("main thread: condition [%d] \n",condition );
}
assert(0 == pthread_mutex_unlock(&mutex));
}
(9)条件变量是程序用来等待某个“状态”为真的机制。
四、条件变量示例代码一
示例一代码中,子线程负责对变量中的 m_iIndex 进行一次自增操作,该子线程先 sleep 1s,以便确保主线程在被唤醒之前条件变量等待中。子线程执行自增操作后,立刻向等待该条件变量的线程发送通知信号。主线程中会去判断该变量,若不大于0,则等待条件变量,发送信号,最终打印该变量的值。
/*************************************************************************
* File Name: CondAndMutex.cpp
* Author: The answer
* Function: Other
* Mail: [email protected]
* Created Time: 2018年12月17日 星期六 06时16分07秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <pthread.h>
#include <istream>
#include <vector>
#include <queue>
#include <cassert>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <time.h>
using namespace std;
#ifndef MAX_LENGTH
#define MAX_LENGTH 10
#endif
typedef struct{
pthread_cond_t m_cond; //条件变量
pthread_mutex_t m_mutex; //互斥量
int m_iIndex; //变量值
}Cond_Mutex;
/**@fn IncreaseDataThread
* @brief 增加变量值
* @param[in] void *pStrData
* @param[in] pStrData->m_mutex 互斥量
* @param[in] pStrData->m_cond 条件变量
* @param[in] pStrData->m_iIndex 变量值
* @param[out] pStrData->m_iIndex 自增过后的变量值
* @return 错误码
**/
void *IncreaseDataThread(void *pStrData)
{
if(!pStrData)
{
printf("InsertDataThread failed. pStrData[%p]\n",pStrData);
return NULL;
}
Cond_Mutex *_pStrData = static_cast<Cond_Mutex*>(pStrData);
sleep(1); //子线程睡眠1S,让主进程加锁m_mutex,并且等待信号的发送
assert(0 == pthread_mutex_lock(&_pStrData->m_mutex));
_pStrData->m_iIndex++;
assert(0 == pthread_cond_signal(&_pStrData->m_cond)); //通知等待该变量状态发送改变的线程
assert(0 == pthread_mutex_unlock(&_pStrData->m_mutex));
printf("InsertDataThread m_iIndex[%d]\n",_pStrData->m_iIndex);
return NULL;
}
int main(int argc,char **argv)
{
Cond_Mutex strCond;
struct timespec strTime;
int iRet = 0;
pthread_t tid;
memset(&strCond,0x00,sizeof(strCond));
assert(0 == pthread_mutex_init(&strCond.m_mutex,NULL)); //初始化条件变量
assert(0 == pthread_cond_init(&strCond.m_cond,NULL)); //初始化互斥量
iRet = pthread_create(&tid,NULL,IncreaseDataThread,(Cond_Mutex*)&strCond);
if(0 != iRet)
{
printf("pthread_create failed. iRet[%d]\n",iRet);
return -1;
}
strTime.tv_sec = time(NULL)+ 3; //5s
strTime.tv_nsec = 0;
assert(0 == pthread_mutex_lock(&strCond.m_mutex));
while(strCond.m_iIndex <= 0)
{
iRet = pthread_cond_timedwait(&strCond.m_cond,&strCond.m_mutex,&strTime);
if(ETIMEDOUT == iRet)
{
printf("main thread time out. iRet[%d]\n",iRet);
//return -1; //如果等待超时,则跳出循环,结束程序
continue;
}else if(0 != iRet)
{
printf("main thread timedwait err. iRet[%d]\n",iRet);
exit(0); //若等待条件变量发送信号失败,则直接结束进程
}
}
if(strCond.m_iIndex >= 0)
{
printf("main thread: m_iIndex[%d] \n",strCond.m_iIndex);
}
assert(0 == pthread_mutex_unlock(&strCond.m_mutex));
assert(0 == pthread_mutex_destroy(&strCond.m_mutex));
assert(0 == pthread_cond_destroy(&strCond.m_cond));
assert(0 == pthread_join(tid,NULL));
return 0;
}
五、等待条件变量 pthread_cond_wait 深度剖析
(1)Pthreads系统确保了在释放互斥量和等待条件变量之间没有线程可以改变“条件变量所等待的状态(谓词)”
在一个条件变量上等待会发生以下“原子操作”:释放相关的互斥量,等待其他线程发给该条件变量的信号(唤醒一个等待者-即等待线程)或广播该条件变量(唤醒所有等待者)。当等待条件变量时,互斥量必须始终锁住;当线程从条件变量等待中醒来时,它重新继续锁住互斥量。
六、若线程发送了一个信号(或广播一个条件变量),而此时没有线程在等待该条件变量,会发生什么?
什么也不会发生。等待条件变量被唤醒函数pthread_cond_wait (或pthread_cond_timedwait)是不会去记录在它们等待之前的所有信号的(包括广播)。而在这之后,若有线程调用了pthread_cond_wait去等待条件变量的到来,则它会一直阻塞等待下去,永不会被唤醒。即它会忽略掉之前的条件变量被发送/广播的情况。
如下示例代码中, 线程waitSignalThread(等待条件变量被唤醒线程)先 sleep 2秒,目的是为了错过线程signalThread(发信号唤醒等待条件变量的线程)中的一次发信号动作,之后,线程waitSignalThread等待 signalThread线程发送条件变量信号过来,但是 signalThread 中之后再也没有发信号动作,因此 线程 waitSignalThread 会一直阻塞下去。
/*************************************************************************
* File Name: CondAndMutex2.cpp
* Author: The answer
* Function: Other
* Mail: [email protected]
* Created Time: 2018年12月18日 星期六 13时01分36秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <pthread.h>
#include <istream>
#include <vector>
#include <queue>
#include <cassert>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <time.h>
using namespace std;
typedef struct{
pthread_cond_t m_cond; //条件变量
pthread_mutex_t m_mutex; //互斥量
int m_a; //变量值
struct timespec m_time; //等待条件变量绝对时间
}Cond_Mutex;
/**@fn signalThread
* @brief 发送信号唤醒等待的条件变量线程
* @param[in] void *pStrData
* @param[in] pStrData->m_mutex 互斥量
* @param[in] pStrData->m_cond 条件变量
* @param[in] pStrData->m_a 变量值
* @param[in] pStrData->m_time 线程运行时间限制
* @param[out] pStrData->m_a 发信号过好赋值-1
* @return 错误码
**/
void *signalThread(void *pStrData)
{
if(!pStrData)
{
fprintf(stderr,"signalThread param is null:[%p]\n",pStrData);
return NULL;
}
Cond_Mutex *pCond = static_cast<Cond_Mutex*>(pStrData);
assert(0 == pthread_mutex_lock(&pCond->m_mutex));
pCond->m_a ++;
assert(0 == pthread_cond_signal(&pCond->m_cond));
fprintf(stdout,"pthread_cond_signal end. m_a[%d]\n",pCond->m_a);
assert(0 == pthread_mutex_unlock(&pCond->m_mutex));
sleep(1);
assert(0 == pthread_mutex_lock(&pCond->m_mutex));
pCond->m_a = -1; //此刻不向等待条件变量被唤醒的线程发送信号(或广播信号)
assert(0 == pthread_mutex_unlock(&pCond->m_mutex));
printf("signalThread m_a[%d]\n",pCond->m_a);
return NULL;
}
/**@fn waitSignalThread
* @brief 等待条件变量被唤醒线程
* @param[in] void *pStrData
* @param[in] pStrData->m_mutex 互斥量
* @param[in] pStrData->m_cond 条件变量
* @param[in] pStrData->m_a 变量值
* @param[out] pStrData->m_a 发信号过后修改的变量值
* @return 错误码
**/
void *waitSignalThread(void *pStrData)
{
if(!pStrData)
{
fprintf(stderr,"waitSignalThread param is null:[%p]\n",pStrData);
return NULL;
}
Cond_Mutex *pCond = static_cast<Cond_Mutex*>(pStrData);
int iRet = 0;
printf("waitSignalThread m_a[%d]\n",pCond->m_a);
assert(0 == pthread_mutex_lock(&pCond->m_mutex));
fprintf(stdout,"lock ok!");
while(pCond->m_a <= 0)
{
iRet == pthread_cond_timedwait(&pCond->m_cond,&pCond->m_mutex,&pCond->m_time);
if(ETIMEDOUT == iRet)
{
printf("waitSignalThread time out. iRet[%d]\n",iRet);
continue;
}else if(0 != iRet)
{
printf("waitSignalThread timedwait err. iRet[%d]\n",iRet);
exit(0); //若等待条件变量发送信号失败,则直接结束进程
}
}
if(pCond->m_a > 0)
{
printf("waitSignalThread m_a[%d]\n",pCond->m_a);
}
assert(0 == pthread_mutex_unlock(&pCond->m_mutex));
return NULL;
}
int main(int argc,char **argv)
{
Cond_Mutex strCondMutex;
pthread_t tid[2] = {0};
int iRet = 0;
memset(&strCondMutex,0x00,sizeof(strCondMutex));
assert(0 == pthread_mutex_init(&strCondMutex.m_mutex,NULL));
assert(0 == pthread_cond_init(&strCondMutex.m_cond,NULL));
strCondMutex.m_time.tv_sec = time(NULL)+ 3; //3s
strCondMutex.m_time.tv_nsec = 0;
iRet = pthread_create(&tid[0],NULL,signalThread,(Cond_Mutex*)&strCondMutex);
if(0!=iRet)
{
perror("pthread_create signalThread");
return -1;
}
sleep(2);
iRet = pthread_create(&tid[1],NULL,waitSignalThread,(Cond_Mutex*)&strCondMutex);
if(0!=iRet)
{
perror("pthread_create waitSignalThread");
return -1;
}
for(int i=0;i<2;++i)
{
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&strCondMutex.m_mutex);
pthread_cond_destroy(&strCondMutex.m_cond);
puts("%%%% over %%%%");
return 0;
}