生产者消费者问题是一个经典的同步问题,相信学习过操作系统的同学都接触过。这里总结一下《UNP vol2》中关于该问题的知识。
互斥锁、条件变量
可以用于线程间同步,可以用于进程将同步(需要将互斥锁、条件变量放入进程间的共享内存区域)。
注意:下面的例子是 N个生产者和1个消费者的问题。
#include
#include
#include
#include
#include
using namespace std;
queue<string> mq;
pthread_mutex_t mq_lock = PTHREAD_MUTEX_INITIALIZER;
void *producer(void *arg)
{
pthread_mutex_lock(&mq_lock);
string message("Message From ");
message += boost::lexical_cast<std::string>(pthread_self());
mq.push(message);
pthread_mutex_unlock(&mq_lock);
return NULL;
}
void *consumer(void *arg)
{
while(!mq.empty())
{
cout << mq.front() << endl;
mq.pop();
}
return NULL;
}
const int pd_thread_num = 5; /* producer thread number */
int main()
{
mq.push("Starting...");
/* start 5 producer */
pthread_t ptids[pd_thread_num], ctid;
for(int i = 0; i < pd_thread_num; i++)
{
if(pthread_create(&ptids[i], NULL, producer, NULL) != 0)
{
cerr << "create producer thread failed." << endl;
}
}
/* wait all producer finished */
for(int i = 0; i < pd_thread_num; i++)
{
pthread_join(ptids[i], NULL);
}
/* start consumer */
if(pthread_create(&ctid, NULL, consumer, NULL) != 0)
{
cerr << "create consumer thread failed." << endl;
}
/* wait consumer finished */
pthread_join(ctid, NULL);
return 0;
}
pthread_cond_wait
的原子性。 pthread_cond_wait
执行之前,要确保关联的互斥锁已经加锁。 pthread_cond_wait
执行时,首先把调用线程放入该条件变量的等待队列,然后将互斥锁解锁。 pthread_cond_wait
成功返回时(其它线程调用pthread_cond_signal或pthread_cond_broadcast
通知了当前线程),互斥锁将再次被加锁。 #include
#include
#include
#include
#include
#include
using namespace std;
queue<string> mq;
/* condition varialbe */
pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *producer(void *arg)
{
string message("Message From ");
message += boost::lexical_cast<std::string>(pthread_self());
while(true)
{
pthread_mutex_lock(&cond_mutex);
mq.push(message);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&cond_mutex);
sleep(1);
}
return NULL;
}
void *consumer(void *arg)
{
while(true)
{
pthread_mutex_lock(&cond_mutex);
while(mq.empty())
{
cout << "consumer wait." << endl;
pthread_cond_wait(&cond, &cond_mutex);
}
cout << mq.front() << endl;
mq.pop();
pthread_mutex_unlock(&cond_mutex);
}
return NULL;
}
const int pd_thread_num = 5; /* producer thread number */
int main()
{
mq.push("Starting...");
/* start 5 producer */
pthread_t ptids[pd_thread_num], ctid;
for(int i = 0; i < pd_thread_num; i++)
{
if(pthread_create(&ptids[i], NULL, producer, NULL) != 0)
{
cerr << "create producer thread failed." << endl;
}
}
/* start consumer */
if(pthread_create(&ctid, NULL, consumer, NULL) != 0)
{
cerr << "create consumer thread failed." << endl;
}
/* wait all producer finished */
for(int i = 0; i < pd_thread_num; i++)
{
pthread_join(ptids[i], NULL);
}
/* wait consumer finished */
pthread_join(ctid, NULL);
return 0;
}
参考
常见问题:pthread_cond_signal会不会导致死锁?
该问题值得一看。摘录如下:
pthread_cond_signal
does not unlock the mutex (it can’t as it has no reference to the mutex, so how could it know what to unlock?) In fact, the signal need not have any connection to the mutex; the signalling thread does not need to hold the mutex, though for most algorithms based on condition variables it will.
pthread_cond_signal
没有进行解锁操作(该函数的参数没有引用互斥锁的地方,所以更不会进行解锁操作了)。signal不必与互斥锁有任何的关系。发送signal的线程不必持有该互斥锁,但是对于大多数基于条件变量的算法会这么做。
pthread_cond_wait
unlocks the mutex just before it sleeps, but then it reaquires the mutex (which may require waiting) when it is signalled, before it wakes up. So if the signalling thread holds the mutex (the usual case), the waiting thread will not proceed until the signalling thread also unlocks the mutex.
pthread_cond_wait
在让线程sleep之前会释放该互斥锁。在它被唤醒之前(有线程调用了signal函数),它需要再次持有该互斥锁。因此,如果发送signal的线程持有该互斥锁,则等待的线程不会继续执行,直到发送signal的线程释放了该互斥锁。
最后,闲的蛋疼,实现了一个让N个线程同时运行的程序,用的就是条件变量的pthread_cond_broadcast
函数,用于唤醒所有sleep的线程。
#include
#include
#include
#include
#include
using namespace std;
volatile bool start = false;
/* condition varialbe */
pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *custom_thread(void *arg)
{
pthread_mutex_lock(&cond_mutex);
while(!start)
{
cout << "Thread " << pthread_self() << " wait..." << endl;
pthread_cond_wait(&cond, &cond_mutex);
}
cout << "Thread " << pthread_self() << " start..." << endl;
pthread_mutex_unlock(&cond_mutex);
return NULL;
}
const int pd_thread_num = 5; /* producer thread number */
void threads_continues()
{
pthread_mutex_lock(&cond_mutex);
pthread_cond_broadcast(&cond);
start = true;
pthread_mutex_unlock(&cond_mutex);
}
int main()
{
/* pre-start threads */
pthread_t tids[pd_thread_num];
for(int i = 0; i < pd_thread_num; i++)
{
if(pthread_create(&tids[i], NULL, custom_thread, NULL) != 0)
{
cerr << "create producer thread failed." << endl;
}
}
/* boradcast to continue all threads*/
sleep(2);
threads_continues();
/* wait all thread finished */
for(int i = 0; i < pd_thread_num; i++)
{
pthread_join(tids[i], NULL);
}
return 0;
}
疑问:pthread_cond_signal
应该放在什么位置?
参考百度百科,该函数可以放在lock和unlock之间,也可以放在unlock之后。没明白各自的优缺点。
相关名词:“惊群效应” “cond_wait队列” “mutex_wait队列”