互斥锁、条件变量:生产者消费者问题

生产者消费者问题是一个经典的同步问题,相信学习过操作系统的同学都接触过。这里总结一下《UNP vol2》中关于该问题的知识。

互斥锁、条件变量 可以用于线程间同步,可以用于进程将同步(需要将互斥锁、条件变量放入进程间的共享内存区域)。

注意:下面的例子是 N个生产者和1个消费者的问题。

  • 互斥锁
    下面的程序有5个生产者线程、1个消费者线程。这些线程共享一个队列mq。首先启动这5个生产者线程,等它们都结束后才启动消费者线程。因此这里只使用了互斥锁来保证生产者线程的同步。
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>

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 <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

using namespace std;

queue<string> mq;
pthread_mutex_t mq_lock = PTHREAD_MUTEX_INITIALIZER;

/* 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(&mq_lock);
        mq.push(message);
        pthread_mutex_unlock(&mq_lock);

        pthread_mutex_lock(&cond_mutex);
        if(!mq.empty())
            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;
}

参考

  • 《UNP vol2》
  • 《Linux高性能服务器编程》
  • http://blog.chinaunix.net/uid-11572501-id-3456343.html

常见问题: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 <iostream>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

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队列”

你可能感兴趣的:(互斥锁、条件变量:生产者消费者问题)