条件变量

作者:CppExplore  网址:http://www.cppblog.com/CppExplore/
为了后面写的《网络模型(二)》,多写一篇关于线程的。线程使用涉及的主要数据结构以及应用框架可以参考http://www.cppblog.com/CppExplore/archive/2008/01/15/41175.html。本文的主要目的是给出linux下实用的线程消息队列实现。
一、linux上线程相关的操作有下面几种:
(1)pthread_t类型的创建、属性创建设置等。
这类具体可以:man pthread_creat; man pthread_attr_init;man pthread_detach;man pthread_join;等查看
(2)pthread_mutex_t类型的操作。
这类具体可以: man pthread_mutex_init可以看到所有相关的操作。
(3)pthread_cond_t类型的操作。同样:man pthread_cond_init。pthread_cond_t的wait和signal操作一定要和pthread_mutex_t的lock、unlock配合使用。类似于此:

pthread_mutex_t mux=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond
=PTHREAD_COND_INITIALIZER;

//wait操作:
pthread_mutex_lock(&mux);
pthread_cond_wait(
&cond,&mux);//睡眠前将内部会执行pthread_mutex_unlock,醒来时内部会执行pthread_mutex_lock
pthread_mutex_unlock(&mux);

//signal操作
pthread_mutex_lock(&mux);
pthread_cond_signal(
&cond);
pthread_mutex_unlock(
&mux);

(4)sem_t类型的操作。同样:man sem_init 这个系列一般是用不到的,太重量级了,也是最强大的一种。

二、linux2.6内核的线程库。2.6内核的默认安装的是redhat公司的NPTL(原生posix线程库),以前内核安装的是LinuxThreads库,两者的简单介绍可以看http://www.ibm.com/developerworks/cn/linux/l-threading.html。不过对于应用者,分析两者的区别和优劣也没什么大意义。这里特别提下NPTL的futex机制。借助该机制,pthread_mutex的性能大大提高,只要不进入竞争态,进程就不会陷入内核态。这点可以自己写示例程序,通过strace -c 跟踪进程的系统调用得以证实,另外还可以证实总是进入内核态的操作有pthread_cond_signal和sem_post。

三、实用的线程消息队列实现。下面设计一个具有线程消息队列的线程封装类。通过上面的分析,我们可以有如下结论:
(1)减少pthread_cond_signal和sem_post的调用,只在有必要的时候调用; 
(2)尽量避免pthread_mutex进入竞争态。增大消息队列的大小,可以有效减少竞态条件的出现。

下面给出一个实用的线程消息队列的实现类,这个类也将是以后《网络模型》文章中用到的线程消息队列类,代码注释请看对私有属性的注释:

class CThreadQueue
{

public:
    CThreadQueue(int queueSize=1024):
        sizeQueue(queueSize),lput(0),lget(0),nFullThread(0),nEmptyThread(0),nData(0)
    {
        pthread_mutex_init(&mux,0);
        pthread_cond_init(&condGet,0);
        pthread_cond_init(&condPut,0);
        buffer=new (void *)[sizeQueue];
    }

    virtual ~CThreadQueue()
    {
        delete[] buffer;
    }

    void * getq()
    {
        void *data;
        pthread_mutex_lock(&mux);
        while(lget==lput&&nData==0)//此处循环判断的原因如下:假设2个线程在getq阻塞,然后两者都被激活,而其中一个线程运行比较块,快速消耗了2个数据,另一个线程醒来的时候已经没有新数据可以消耗了。另一点,man pthread_cond_wait可以看到,该函数可以被信号中断返回,此时返回EINTR。为避免以上任何一点,都必须醒来后再次判断睡眠条件。更正:pthread_cond_wait是信号安全的系统调用,不会被信号中断。
        {
            nEmptyThread++;
            pthread_cond_wait(&condGet,&mux);
            nEmptyThread--;     
        }

            
        data=buffer[lget++];
        nData--;
        if(lget==sizeQueue)
        {
            lget=0;
        }

        if(nFullThread) //必要时才进行signal操作,勿总是signal
        {
            pthread_cond_signal(&condPut);    
        }

        pthread_mutex_unlock(&mux);
        return data;
    }

    void putq(void *data)
    {
        pthread_mutex_lock(&mux);
        while(lput==lget&&nData)
        
            nFullThread++;
            pthread_cond_wait(&condPut,&mux);
            nFullThread--;
        }

        buffer[lput++]=data;
        nData++;
        if(lput==sizeQueue)
        {
            lput=0;
        }

        if(nEmptyThread)
        {
            pthread_cond_signal(&condGet);
        }

        pthread_mutex_unlock(&mux);
    }

private:
    pthread_mutex_t mux;
    pthread_cond_t condGet;
    pthread_cond_t condPut;

    void * * buffer;    //循环消息队列
    int sizeQueue;        //队列大小
    int lput;        //location put  放数据的指针偏移
    int lget;        //location get  取数据的指针偏移
    int nFullThread;    //队列满,阻塞在putq处的线程数
    int nEmptyThread;    //队列空,阻塞在getq处的线程数
    int nData;        //队列中的消息个数,主要用来判断队列空还是满
}
;

 

下面给出这个线程消息队列的一个使用举例:

#include <pthread.h>
#include 
<stdio.h>
#include 
<unistd.h>
#include 
<stdlib.h>
CThreadQueue queue;//使用的时候给出稍大的CThreadQueue初始化参数,可以减少进入内核态的操作。

void * produce(void * arg)
{
    int i=0;
    pthread_detach(pthread_self());
    while(i++<100)
    {
        queue.putq((void *)i);
    }

}

void *consume(void *arg)
{
    int data;
    while(1)
    {
        data=(int)(queue.getq());
        printf("data=%d\n",data)
    }

}

int main()
{    pthread_t pid;
    int i=0;

    while(i++<3)
        pthread_create(&pid,0,produce,0);
    i=0;
    while(i++<3)
        pthread_create(&pid,0,consume,0);
    sleep(300);
}

posted on 2008-03-20 14:33 cppexplore 阅读(6242) 评论(12)  编辑 收藏 引用


评论

# re: 【原创】系统设计之 线程(二) 2008-04-10 11:56 true
CThreadQueue写得简单明了,实用性强,另外 
1.getq,putq有timeout的重载函数就更使用了,ace里面就是这么搞得,否则服务程序退出就是个问题,或者其它退出方式? 
2.在析构函数里面应该destroy分配的mutex,conditon,呵呵,瑕不掩瑜。 
3.你这系列的文章很使用,强烈支持写下去!  回复  更多评论 
  

# re: 【原创】系统设计之 线程(二)[未登录] 2008-04-10 13:42 cppexplore
多谢提醒,2的问题还真是遗漏了,1的问题的确也应该考虑,呵呵 

  回复  更多评论 
  

# re: 【原创】技术系列之 线程(二) 2008-10-23 13:10 cui
写得不错..提醒一下..
pthread_cond_wait 不会被信号中断的.
man pthread_cond_wait:
These functions shall not return an error code of [EINTR]  回复  更多评论 
  

# re: 【原创】技术系列之 线程(二)[未登录] 2008-10-23 13:43 cppexplore
@cui 
多谢。最近看到了,这个是信号安全的系统调用。  回复  更多评论 
  

# re: 【原创】技术系列之 线程(二) 2009-01-09 00:23 ssharry
pthread_mutex_lock(&mux); 
while(lget==lput&&nData==0)//此处循环判断的原因如下:假设2个线程在getq阻塞,然后两者都被激活,而其中一个线程运行比较块,快速消耗了2个数据,另一个线程醒来的时候已经没有新数据可以消耗了。另一点,man pthread_cond_wait可以看到,该函数可以被信号中断返回,此时返回EINTR。为避免以上任何一点,都必须醒来后再次判断睡眠条件。更正:pthread_cond_wait是信号安全的系统调用,不会被信号中断。 


请教一下,本类中,何时会出现2个线程同时被激活的情况?  回复  更多评论 
  

# re: 【原创】技术系列之 线程(二)[未登录] 2009-01-09 09:23 cppexplore
@ssharry 
其它线程快速调用putq两次,如果有2个线程在getq处阻塞,就会被同时激活,而完全有可能,其中一个被激活的线程获取到了cpu,快速处理了2个消息。  回复  更多评论 
  

# re: 【原创】技术系列之 线程(二) 2009-01-09 20:30 
兄弟,很有借鉴意义啊,而且很简单实用,收下了~~
  回复  更多评论 
  

# re: 【原创】技术系列之 线程(二) 2009-08-31 10:48 shiqyn
buffer=new (void *)[sizeQueue]; 这个有错,应该写成 buffer=new void *[sizeQueue];  回复  更多评论 
  

# re: 【原创】技术系列之 线程(二) 2009-08-31 10:48 shiqyn
buffer=new (void *)[sizeQueue]; 
应该写成 buffer=new void *[sizeQueue];  回复  更多评论 
  

# re: 【原创】技术系列之 线程(二) 2009-10-29 13:16 laohu
尽量避免pthread_mutex进入竞争态。增大消息队列的大小,可以有效减少竞态条件的出现。 
????????????????????????????????????/ 
互斥变量的竞争和队列长度有什么相关? 
无论队列长短,凡是要操作队列的动作,都必须获取互斥锁啊。 
比如,consumer线程都在工作,producer就要不断写队列最后把队列给写满了,这时候队列长点能够多缓冲数据。 
条件变量的特点就是没有wait的情况下signal会被丢弃,比如consumer一直忙,producer的signal就会被丢弃的,但是没有关系,只有在队列空情况下producer才需要cond wait,有数据情况下根本不需要wait直接操作即可(mutex保证了互斥)。 
信号量的每次操作都不会被丢弃,相对系统代价应该比cond高了一些的。  回复  更多评论 
  

# re: 【原创】技术系列之 线程(二)[未登录] 2009-10-30 08:51 cppexplore
@laohu 
"尽量避免pthread_mutex进入竞争态。增大消息队列的大小,可以有效减少竞态条件的出现。 " 

前一个句号:减少有人拿到了互斥锁的时候,再有人申请,此时后来者将陷入内核等待。 

后一个句号:增大消息队列长度,可以减少pthread_cond_signal的调用频率,该调用总是陷入内核。 

“互斥变量的竞争和队列长度有什么相关? ”,没啥关系,上面中间是句号, 你当成逗号了吧,呵呵。 
  回复  更多评论 
  
posted @ 2013-05-23 20:47  tangr206  阅读(335)  评论(0编辑  收藏  举报