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