0
点赞
收藏
分享

微信扫一扫

操作系统并发性(三):条件变量

老王420 2022-03-13 阅读 90

为什么引入条件变量

考虑如下情形:A线程负责删除链表中的一个元素,B线程负责往链表中插入一个元素。对于一个空链表,只有B线程先执行过了,A线程能够执行。

条件变量适用于这样的情形:两个线程完成的任务之间有明确的先后顺序。

条件变量API

//数据结构
pthread_cond_t cond;
pthread_mutex_t mutex;
//初始化
pthread_cond_init(&cond, NULL);
//等待,需要提供锁,因为要将锁释放,详见后续
pthread_cond_wait(&cond, &mutex);
//发射信号
pthread_cond_signal(&cond);

条件变量的使用

条件变量的使用需要配合一个锁和一个done变量。为什么需要?

考虑一个父线程等待子线程完成的实例:

#include <cstdio>
#include <pthread.h>

int done = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void thre_exit() //son exit
{
    pthread_mutex_lock(&mutex);
    done = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
}
void thr_join() //father wait son
{
    pthread_mutex_lock(&mutex);
    while (done == 0)
        pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);
}
void* sonThread(void* arg)
{
    printf("son:begin\n");
    thre_exit();
    return NULL;
}
int main()
{
    printf("parent:begin\n");
    pthread_t son;
    pthread_create(&son, NULL, sonThread, NULL);
    thr_join();
    printf("Parent:end\n");
    return 0;
}

为什么需要done

如果子线程在父线程之前执行,那么可能父线程还没有调用wait,子线程已经signal了,父线程会长眠不醒。

为什么需要锁

这里存在一个微妙的竞态条件。如果thr_join执行时,在wait前发生了不合时宜的中断,那么如果子线程执行thr_exit,就会导致父线程长睡不醒。

为什么只用一个done不行

最简单的方案就是父线程与子线程共享一个变量done,进入子线程之前设置done为0, 当done==1时,意味着子线程执行完毕。

该方案的问题出在父线程的自旋上,性能太差。

为什么join中要用while

这个算是一种好的习惯,就本例子而言while与if是等价的。但有时会出现多个等待线程竞争的情况,即使从wait中醒来,也不应该执行。

生产者、消费者问题(有界缓冲区)

假设有一个或多个生产者进程与一个或多个消费者线程。生产者将生成的数据放入缓冲区,消费者将缓冲区的数据取走。

使用条件变量解决有界缓冲区问题:

存在一个生产者线程与两个消费者线程,缓冲区的大小为8,生产者执行一次往缓冲区加入10个数,一个消费者执行一次从缓冲区中读取5个数。
可以验证了full与empty条件变量的发送、等待。

#include <iostream>
#include <algorithm>
#include "pthread.h"


const int MAX = 8;
int fill_ptr = 0, use_ptr = 0;
int cnt = 0;
int buffer[MAX];

void put(int value)
{
    buffer[fill_ptr] = value;
    fill_ptr = (fill_ptr + 1) % MAX;
    cnt++;
}
int get()
{
    int ret = buffer[use_ptr];
    use_ptr = (use_ptr + 1) % MAX;
    cnt--;
    return ret;
}

pthread_cond_t empty, full;
pthread_mutex_t lock;       //why lock?

void* producer(void* arg)
{
    printf("This is producer\n");
    for (int i = 0; i < 10; i++)
    {
        pthread_mutex_lock(&lock);
        while (cnt == MAX)
            pthread_cond_wait(&empty, &lock);

        put(i);
        printf("producer:%d\n",i);
        pthread_cond_signal(&full);
        pthread_mutex_unlock(&lock);

    }
}
void* consumer(void* arg)
{
    char* name = static_cast<char*>(arg);
    printf("This is consumer%s\n", name);
    for (int i = 0; i < 5; i++)
    {
        pthread_mutex_lock(&lock);
        while (cnt == 0)
            pthread_cond_wait(&full, &lock);
        
        int tmp = get();
        pthread_cond_signal(&empty);
        pthread_mutex_unlock(&lock);
        printf("%s:%d\n", name, tmp);
    }
}

int main()
{
    pthread_t pro, cons1, cons2;
    char A[] = "A", B[] = "B";
    pthread_cond_init(&empty, NULL);
    pthread_cond_init(&full, NULL);
    pthread_create(&pro, NULL, producer, NULL);
    pthread_create(&cons1, NULL, consumer, (void*)A);
    pthread_create(&cons2, NULL, consumer, (void*)B);

    pthread_join(pro, NULL);
    pthread_join(cons1, NULL);
    pthread_join(cons2, NULL);
}

运行结果

在这里插入图片描述

举报

相关推荐

0 条评论