pthread的同步:条件变量之条件

关于条件变量这个名字为什么叫条件变量,我不知道。可能是历史原因吧!在我眼里,这个变量既无条件,亦非变量,简直就是无条件常量。呵,我感到无数鄙夷的目光向我射来。还是委婉些,我收回,这句话。我想,条件变量的条件,应该是让用户自己填充的,既然用户自己没有填充,系统干跑,自然就没有条件,也无所谓变量。

现在就来填充一下。前面一直强调,wait线程是全文加锁的,因此可以从mutex测试wait线程是否睡眠。然而这个要求有些苛刻。由于应用场景的复杂性,应用程序不想让它全文加锁。于是应用程序自己选择了“条件”。可能,这就是条件变量的真正含义吧。现在就去掉这个限制。

重写一遍上次的例子很无聊,这次就用C++的class封装一下。当然只保证在本文的例子中能用。严谨的封装很烦,且与本文讨论的问题无关。

struct condvar_dat {
        pthread_mutex_t mutex;
        pthread_mutex_t idle;
        volatile int sync;
        volatile bool enabled;
        pthread_cond_t cond;
};


class user_condvar: condvar_dat {
        void idle_sync() { while(sync) sched_yield(); }
        void enable_sync() { while(!enabled) sched_yield(); }
public:
        user_condvar() {
                pthread_mutexattr_t mat;
                pthread_mutexattr_init(&mat);
                pthread_mutexattr_settype(&mat, PTHREAD_MUTEX_NORMAL);
                pthread_mutex_init(&mutex,&mat);
                pthread_mutex_init(&idle,&mat);
                pthread_mutexattr_destroy(&mat);
                pthread_cond_init(&cond, NULL);
                sync=0;
                enabled=false;
        }

        ~user_condvar(){
                pthread_cond_destroy(&cond);
                pthread_mutex_destroy(&idle);
                pthread_mutex_destroy(&mutex);
        }


        void enable() { pthread_mutex_lock(&mutex); enabled=true;}
        void disable() { enabled=false; pthread_mutex_unlock(&mutex); }
        bool ifenable() {return enabled;}

        void sleep() {idle_sync(); pthread_mutex_lock(&idle);
                        pthread_cond_wait(&cond, &mutex);}
        void wakeup() {enable_sync(); pthread_mutex_lock(&mutex);
                        pthread_cond_signal(&cond); sync=1;
                        pthread_mutex_unlock(&mutex); }
        void sync_wait() {pthread_mutex_lock(&idle);
                        pthread_mutex_unlock(&idle); sync=0;}
        void sync_release() {pthread_mutex_unlock(&idle);}
};

这里使用分段锁,idle, mutex, 和sync。都是前文讨论过的。新增了一个enable。这个enable就是“条件”。如果这个enable被使能,表明wait线程进入了“条件”区,可以和signal线程同步。也就是我睡眠,你发signal来叫我。当然,signal线程也要检查wait线程有没有做这个使能,如果还没有,又必须发signal,那么需要同步等待wait线程使能。这个逻辑直接做在wakeup()里面了。当然,还要标出危险区。

现在,应用程序“无敌”了:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include "condvar.h"

struct msg
{
    int num;
    struct msg *next;
};

struct msg *head = NULL;
struct msg *temp = NULL;



user_condvar cond;

void *producer(void *arg)
{
    int len;

    while (1)  {

        temp = (struct msg*)malloc(sizeof(struct msg));
        temp->num = rand() % 100 + 1;
        temp->next = head;
        head = temp;
        len=0;
        while(temp) { len++; temp=temp->next;}
        printf("---producered(%d)---%d\n", len, head->num);

        cond.wakeup();
        cond.sync_wait();
    }

    return NULL;
}

void *consumer(void *arg)
{
    while (1)      {
        cond.enable();
        cond.sleep();
        temp = head;
        head = temp->next;
        printf("------------------consumer--%d\n", temp->num);
        free(temp);
        temp = NULL;
        cond.disable();
        cond.sync_release();
    }

    return NULL;
}

int main(void)
{
    pthread_t ptid, ctid;


    srand(time(NULL));


    pthread_create(&ctid, NULL, consumer, NULL);
    pthread_create(&ptid, NULL, producer, NULL);


    pthread_join(ptid, NULL);
    pthread_join(ctid, NULL);

    return 0;
}

producer做了wakeup()之后,会阻塞在idle锁上, 这是cond.sync_wait();语句的作用;或者根本没有被OS放行。这无所谓。consumer被唤醒后去处理producer留给它的数据,然后 cond.sync_release(); 去解锁producer,这时候,它已经脱离“条件”区了。接下来producer会继续去生产数据,而consumer也一路畅行直到下一次睡眠。注意consumer的cond.disable();和cond.sync_release();两个语句,如果次序用反,会产生竞争条件。producer有可能会抢在consumer做disable();之前检查enabled变量,误判consumer仍然enable。然后去发signal扑了个空。等到consumer重新进入睡眠时,signal已经错过了。producer等待consumer的sync_release()放行,而consumer却在睡眠中。就死锁了。

变化一:如果consumer线程的 cond.enable(); 放在while之前,并且删除cond.disable();这句,就是全部在条件区,也就是全文加锁。就跟上文中的对应例子完全一样了。

变化二:如果producer想允许数据一定的积累以提高效率,也可以有条件的绕过
cond.wakeup();
cond.sync_wait();
这两句。同时,consumer也要相应增加一步腾空积累数据的功能。比如下面那样:

void *producer(void *arg)
{
    int len;

    while (1)  {

        temp = (struct msg*)malloc(sizeof(struct msg));
        temp->num = rand() % 100 + 1;
        temp->next = head;
        head = temp;
        len=0;
        while(temp) { len++; temp=temp->next;}
        printf("---producered(%d)---%d\n", len, head->num);

        if(cond.ifenable() || len>=10) {
        cond.wakeup();
        cond.sync_wait();
        }
    }

    return NULL;
}

void *consumer(void *arg)
{
    while (1)      {
        cond.enable();
        cond.sleep();
        while((temp = head)!=NULL) {
        head = temp->next;
        printf("------------------consumer--%d\n", temp->num);
        free(temp);
        }
        temp = NULL;
        cond.disable();
        cond.sync_release();
    }

    return NULL;
}

这时候producer向consumer发送的数据始终控制在 1~10个。运行结果就不贴上了。可以自己跑下看结果。

最后,再次给出读写线程的例子。这次用了刚写的C++条件变量封装。

#include <stdio.h>
#include <string.h>
#include <setjmp.h>
#include <stdlib.h>
#include <pthread.h>

#include "condvar.h"

typedef int BOOL;
#define TRUE    1
#define FALSE   0


typedef struct _Context_ {
        int finish;
        struct {
                user_condvar cond;
                int state;
        } thread[2];
} Context;

#define  WRITE_THREAD 0
#define  READ_THREAD 1

Context gCtx;
static int next_thread;
int run;

void resume(int i)
{
        gCtx.thread[i].cond.wakeup();
        gCtx.thread[i].cond.sync_wait();
}



void yield(int i)
{
//printf("thread %d yield\n", i);
        gCtx.thread[i].cond.sync_release();
        gCtx.thread[i].cond.sleep();
}

#define MAXBUFS 10
struct buffer {
        char buf[MAXBUFS][128];
        int pos;
        int bc;
        int eobf;
};
struct buffer bx;

void bx_read(char *s, int n)
{
  restart:
        if (bx.bc) {
        strncpy(s, bx.buf[bx.pos], n);
        s[n - 1] = '\0';
        bx.bc--;
        bx.pos++;
        if (bx.pos >= MAXBUFS)
                bx.pos = 0;
                return;
        } else if (bx.eobf) {
                gCtx.finish = 1;
                gCtx.thread[run].state = 1;
                yield(READ_THREAD);
        } else {
                yield(READ_THREAD);
                goto restart;
        }
}

void bx_write(char *s, int n)
{
        int wpos;
        while (bx.bc >= MAXBUFS) {
                yield(WRITE_THREAD);
        }
        wpos = bx.pos + bx.bc;
        if (wpos >= MAXBUFS)
        wpos -= MAXBUFS;

        if (n < 128) {
                strcpy(bx.buf[wpos], s);
        }
        else {
                strncpy(bx.buf[wpos], s, 128);
                bx.buf[wpos][127] = '\0';
        }
        bx.bc++;
}

typedef void *(*pf) (void *);
BOOL start_coroutine(pf func, void *arg);
void coroutine_read(void *arg);

void coroutine_write(void *arg)
{
        FILE *fp;
        char s[128];
        int len;
        int i = (int) arg;

        i = WRITE_THREAD;
        gCtx.thread[i].cond.enable();
        gCtx.thread[i].cond.sleep();

        fp = fopen("b.c", "r");
        if (!fp) {
                printf("can not open `b.c'\n");
                exit(0);
        }

        while (fgets(s, 128, fp) != NULL) {
                len = strlen(s);
                if (len) {
                        --len;
                        if (s[len] == '\n') s[len] = '\0'; else ++len;
                }
                if (len) {
                        --len;
                        if (s[len] == '\r') s[len] = '\0'; else ++len;
                }
                bx_write(s, len + 1);
        }
        bx.eobf = 1;
        gCtx.thread[WRITE_THREAD].state = 1;
        fclose(fp);
printf("[WRITE_THREAD FINISHED]\n");
        yield(WRITE_THREAD);
}

void coroutine_read(void *arg)
{
        char buf[128];
        int i = (int) arg;

        i = READ_THREAD;
        gCtx.thread[i].cond.enable();
        gCtx.thread[i].cond.sleep();

        while (!bx.eobf || bx.bc) {
                bx_read(buf, 128);
                printf("%s\n", buf);
        }
        gCtx.thread[READ_THREAD].state = 1;
printf("[READ_THREAD FINISHED]\n");
        yield(READ_THREAD);
}


BOOL start_coroutine(pf func, void *arg)
{
        int thread_id;
        pthread_t pt_id;

        thread_id = next_thread++;

        pthread_create(&pt_id, NULL, func, (void *) thread_id);
        pthread_detach(pt_id);
        return TRUE;
}


int sched()
{
        int s;
        if (run == 0) s = 1; else s = 0;
        if (gCtx.thread[s].state == 0) run = s;
        return run;
}

int main()
{
        int i;

        start_coroutine((pf) coroutine_write, NULL);
        start_coroutine((pf) coroutine_read, NULL);

        run = 1;

        while (TRUE) {
                i = sched();
printf("sched thread %d to run\n", i);
                if (gCtx.thread[i].state == 0) {
                        resume(i);

                } else break;
        }
        printf("done\n");
        return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值