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

为什么引入条件变量

考虑如下情形: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中醒来,也不应该执行。

信号的Mesa释义:发信号给线程只是唤醒它们,暗示状态发生了变化,并不保证它运行之前状态一直是期望的情况。

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

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

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

存在一个生产者线程与两个消费者线程,缓冲区的大小为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
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值