[多线程]环形缓冲区以及多线程条件同步

1、环形缓冲区(下面生产者消费者的例子使用)

  使用一段内存空间作为缓冲区,维护两个指针,一是读指针,指向缓冲空间的第一个可读位置;二是写指针,指向空间的第一个空位置。读取一个数据后,读指针+1,当指针位置超出缓冲区域则指向缓冲区域的头位置(置0);写入一个数据后,写指针+1,当指针位置超出缓冲区域则指向缓冲区域的头位置(置0);由于空间循环利用,故称为环形缓冲区。

  空间满和空都是读指针位置等于写指针位置。如何判断空间已满?空?方法1:废弃一个缓冲空间不用,当写指针+1等于读指针的时候(意思是写指针多跑一圈快要赶上读指针),此时表明空间已满。方法2:维护一个变量记录缓冲区使用大小,当大小等于缓冲区大小,则为满;当大小为0时为空。

  环形缓冲区多用于多线程缓冲。相对于队列,它的优点是不必新增与释放空间,所有空间重复利用,只需维护两个指针。

2、多线程同步

初始化条件变量:pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);

第一个参数为条件变量的地址,第二个参数设置为NULL表明默认参数设置,也可设为已初始化的条件变量指针,复制参数设置。返回0为初始化成功。

条件变量阻塞:pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);

第一个参数设置为需要阻塞的条件变量;第二个参数为用于阻塞的互斥变量

建议使用方式:

pthread_mutex_lock();
    while(condition_is_false)
        pthread_cond_wait();
pthread_mutex_unlock();

一旦则该条件进入阻塞状态,等待pthread_cond_broadcast (pthread_cond_t *cond)(唤醒全部,慎用) ;与pthread_cond_signal (pthread_cond_t *cond)(唤醒其中一个) ;唤醒条件,解除阻塞。

解除条件阻塞:pthread_cond_signal应在互斥保护下使用,否则可能会在检测condition_is_false后与pthread_cond_wait前调用,解除没阻塞的条件。成功返回0。

解除条件变量与当前状态的关联:pthread_cond_destroy(pthread_cond_t *cond);

生产者与消费者的例子:

#define BUFFER_SIZE 16 // 缓冲区数量

struct prodcons
{
    // 缓冲区相关数据结构
    int buffer[BUFFER_SIZE]; /* 实际数据存放的数组*/
    pthread_mutex_t lock; /* 互斥体lock 用于对缓冲区的互斥操作 */
    int readpos, writepos; /* 读写指针*/
    pthread_cond_t notempty; /* 缓冲区非空的条件变量 */
    pthread_cond_t notfull; /* 缓冲区未满的条件变量 */
    int size;/*缓冲区当前大小*/
};
/* 初始化缓冲区结构 */
void init(struct prodcons *b)
{
    pthread_mutex_init(&b->lock, NULL);
    pthread_cond_init(&b->notempty, NULL);
    pthread_cond_init(&b->notfull, NULL);
    b->readpos = 0;
    b->writepos = 0;
    b->size=0;
}
/* 将产品放入缓冲区,这里是存入一个整数*/
void put(struct prodcons *b, int data)
{
    pthread_mutex_lock(&b->lock);
    /* 等待缓冲区未满*/
    while(b->size==BUFFER_SIZE)//if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
    {
        pthread_cond_wait(&b->notfull, &b->lock);//进入等待后会释放互斥,使其他线程进入互斥改变状态
    }
    /* 写数据,并移动指针 */
    b->buffer[b->writepos] = data;
    b->writepos++;
    if (b->writepos >= BUFFER_SIZE)
        b->writepos = 0;
    b->size++;
    /* 设置缓冲区非空的条件变量*/
    pthread_cond_signal(&b->notempty);
    pthread_mutex_unlock(&b->lock);
} 
/* 从缓冲区中取出整数*/
int get(struct prodcons *b)
{
    int data;
    pthread_mutex_lock(&b->lock);
    /* 等待缓冲区非空*/
    while(b->size==0)//if (b->writepos == b->readpos)//此时缓冲区为空
    {
        pthread_cond_wait(&b->notempty, &b->lock);
    }
    /* 读数据,移动读指针*/
    data = b->buffer[b->readpos];
    b->readpos++;
    if (b->readpos >= BUFFER_SIZE)
        b->readpos = 0;
    b->size--;
    /* 设置缓冲区未满的条件变量*/
    pthread_cond_signal(&b->notfull);
    pthread_mutex_unlock(&b->lock);
    return data;
}

/* 测试:生产者线程将1 到10000 的整数送入缓冲区,消费者线
   程从缓冲区中获取整数,两者都打印信息*/
#define OVER ( - 1)
struct prodcons buffer;
void *producer(void *data)
{
    int n;
    for (n = 0; n < 10000; n++)
    {
        printf("%d --->\n", n);
        put(&buffer, n);
    } put(&buffer, OVER);
    return NULL;
}

void *consumer(void *data)
{
    int d;
    while (1)
    {
        d = get(&buffer);
        if (d == OVER)
            break;
        printf("--->%d \n", d);
    }
    return NULL;
}

int main(void)
{
    pthread_t th_a, th_b;
    void *retval;
    init(&buffer);
    /* 创建生产者和消费者线程*/
    pthread_create(&th_a, NULL, producer, 0);
    pthread_create(&th_b, NULL, consumer, 0);
    /* 等待两个线程结束*/
    pthread_join(th_a, &retval);
    pthread_join(th_b, &retval);
    return 0;
}

 

 解释一下pthread_cond_wait的工作流程

1、pthread_cond_wait必须与pthread_mutex_t协同使用。首先,pthread_mutex_lock(&b->lock);

2、程序查看了缓冲区中,发现缓冲区状态不适合操作,即调用pthread_cond_wait。

3、调用pthread_cond_wait后,mutex的锁马上解除,以便其他线程进行操作,改变缓冲区。

4、mutex解锁后,线程2得以进入改变缓冲区,改变后调用pthread_cond_signal(&b->notfull);唤醒阻塞的线程1.

5、被唤醒后,pthread_cond_wait()先对mutex上锁,再返回。上锁是为了与接下来对缓冲区操作后的解锁相匹配。

 

最近做多线程数据传输程序时出现了“线程饿死”的现象,在这里说明一下:

  当一个线程释放互斥后,CPU时间片还没到时,他又重新进入互斥,使得另外一个生产者线程完全没有机会得到CPU运行权,导致线程无法执行。解决的办法是在释放互斥锁后添加usleep(1),强迫线程进入阻塞,交出CPU运行权,使其他的线程有机会运行

pthread_cond_wait总是与一个布尔判断式相联在一起,如果判断式为真,则线程继续执行,然而pthread_cond_wait返回时无法保证判断式是真是假,因此需要重新判断。

 

pthread_cond_wait与while循环结合使用来检查判断式:

之前在判断size大小的时候使用了if,导致出现了死锁。上网查了文献,发现pthread_cond_wait应该使用while循环,因为解锁的时候不能保证if的条件成立。就像有两个生产者线程的条件下,一个生产者判断BUFFER没空位后被阻塞了,消费者取走BUFFER的货物后唤醒了这个生产者,但此时,有可能此生产者的时间片用完了,CPU切换到第二个生产者去,第二个生产者又把BUFFER填满了。如果第一个生产者使用的是if,被唤醒后没有再判断size,就直接到已满的BUFFER上操作造成错误了。
   When using condition variables there is always a Boolean predicate involving shared variables associated with 
   each condtion wait that is ture if the thread should proceed...
   Since the return does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return.

强烈建议pthread_cond_wait与while循环结合使用来检查判断式

   It is thus recommened that a condition wait be enclosed in the equivalent of a "while loop" that checks the predicate.

posted @ 2013-06-18 21:55  iyjhabc  阅读(5806)  评论(1编辑  收藏  举报