专栏首页开发与安全linux网络编程之posix 线程(四):posix 条件变量与互斥锁 示例生产者--消费者问题

linux网络编程之posix 线程(四):posix 条件变量与互斥锁 示例生产者--消费者问题

一、posix 条件变量

一种线程间同步的情形:线程A需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执行。

在pthread库中通过条件变量(Condition Variable)来阻塞等待一个条件,或者唤醒等待这个条件的线程。Condition Variable用pthread_cond_t类型的变量表示,和Mutex的初始化和销毁类似,pthread_cond_init函数初始化一个Condition Variable,attr参数为NULL则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。如果ConditionVariable是静态分配的,也可以用宏定义PTHEAD_COND_INITIALIZER初始化,相当于用pthread_cond_init函数初始化并且attr参数为NULL。

一个Condition Variable总是和一个Mutex搭配使用的。一个线程可以调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数做以下三步操作: 1. 释放Mutex 2. 阻塞等待 3. 当被唤醒时,重新获得Mutex并返回

注意:3个操作是原子性的操作,之所以一开始要释放Mutex,是因为需要让其他线程进入临界区去更改条件,或者也有其他线程需要进入临界区等待条件。

一个线程可以调用 pthread_cond_signal 唤醒在某个Condition Variable上等待的第一个线程,也可以调用 pthread_cond_broadcast 唤醒在这个Condition Variable上等待的所有线程。

上面的函数具体可以man 一下。

二、条件变量使用规范

(一)、等待条件代码 pthread_mutex_lock(&mutex);

while (条件为假)

pthread_cond_wait(cond, mutex);

修改条件 pthread_mutex_unlock(&mutex); (二)、给条件发送通知代码 pthread_mutex_lock(&mutex); 设置条件为真 pthread_cond_signal(cond); pthread_mutex_unlock(&mutex);

假设第一段用于消费者线程,第二段用于生产者线程。为什么消费者线程要用while 而不是if 就可以呢?在man pthread_cond_wait 有一句:If a signal is delivered to a thread waiting for a condition variable, upon return from the signal handler the thread resumes waiting for the condition variable as if it was not interrupted, or it shall return zero due to spurious wakeup.

即是说如果正在等待条件变量的一个线程收到一个信号,从信号处理函数返回的时候线程应该重新等待条件变量就好象没有被中断一样,或者被虚假地唤醒返回0。如果是上述情形,那么其实条件并未被改变,那么此时如果没有继续判断一下条件的真假就继续向下执行的话,修改条件将会出现问题,所以需要使用while 循环再判断一下,如果条件还是为假必须继续等待。

注:在多处理器系统中,pthread_cond_signal 可能会唤醒多个等待条件的线程,这也是一种spurious wakeup。

当生产者线程较多,即生产得比较快,在这边假设是无界的缓冲区(比如链表),可以不停地生产,使用pthread_cond_signal  发出通知的时候,如果此时没有消费者线程在等待条件,那么这个通知将被丢弃,但也不影响整体代码的执行,没有消费者线程在等待,说明产品资源充足,即while 判断失败,不会进入等待状态,直接消费产品(即修改条件)。

三、生产者消费者问题 

#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

#define CONSUMERS_COUNT 2
#define PRODUCERS_COUNT 1

pthread_mutex_t g_mutex;
pthread_cond_t g_cond;

pthread_t g_thread[CONSUMERS_COUNT + PRODUCERS_COUNT];

int nready = 0;

void *consume(void *arg)
{
    int num = (int)arg;
    while (1)
    {
        pthread_mutex_lock(&g_mutex);
        while (nready == 0)
        {
            printf("%d begin wait a condtion ...\n", num);
            pthread_cond_wait(&g_cond, &g_mutex);
        }

        printf("%d end wait a condtion ...\n", num);
        printf("%d begin consume product ...\n", num);
        --nready;
        printf("%d end consume product ...\n", num);
        pthread_mutex_unlock(&g_mutex);
        sleep(1);
    }
    return NULL;
}

void *produce(void *arg)
{
    int num = (int)arg;
    while (1)
    {
        pthread_mutex_lock(&g_mutex);
        printf("%d begin produce product ...\n", num);
        ++nready;
        printf("%d end produce product ...\n", num);
        pthread_cond_signal(&g_cond);
        printf("%d signal ...\n", num);
        pthread_mutex_unlock(&g_mutex);
        sleep(1);
    }
    return NULL;
}

int main(void)
{
    int i;

    pthread_mutex_init(&g_mutex, NULL);
    pthread_cond_init(&g_cond, NULL);


    for (i = 0; i < CONSUMERS_COUNT; i++)
        pthread_create(&g_thread[i], NULL, consume, (void *)i);

    sleep(1);

    for (i = 0; i < PRODUCERS_COUNT; i++)
        pthread_create(&g_thread[CONSUMERS_COUNT + i], NULL, produce, (void *)i);

    for (i = 0; i < CONSUMERS_COUNT + PRODUCERS_COUNT; i++)
        pthread_join(g_thread[i], NULL);

    pthread_mutex_destroy(&g_mutex);
    pthread_cond_destroy(&g_cond);

    return 0;
}

在上面程序中,++nready 就当作是生产产品了,--nready就当作是消费产品了,跟前面文章所不同的是,这里没有缓冲区大小的概念,可以当作是无界缓冲区,生产者可以一直生产,但消费者只有当有产品的时候才能消费,否则就得等待,等待结束的条件就是nready > 0;上面也说过当生产得比较快(生产者线程多)的时候,也有可能消费者线程一直不存在等待的状态,因为nready 的值很大,即产品资源很多。现在设置的是2个消费者线程和1个生产者线程,所以动态输出来看一般是2个消费者线程轮流等待。

参考:

《linux c 编程一站式学习》

《UNP》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • linux网络编程之posix 线程(二):线程的属性和 线程特定数据 Thread-specific Data

    一、posix 线程属性 POSIX 线程库定义了线程属性对象 pthread_attr_t ,它封装了线程的创建者可以访问和修改的线程属性。主要包括如下属性:...

    s1mba
  • linux网络编程之posix 线程(一):线程模型、pthread 系列函数 和 简单多线程服务器端程序

    一、线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属于1:1模型。 (一)、N:1用户线程模型 “线...

    s1mba
  • linux网络编程之posix 线程(三):posix 匿名信号量与互斥锁 示例生产者--消费者问题

    一、posix 信号量 信号量的概念参见这里。前面也讲过system v 信号量,现在来说说posix 信号量。 system v 信号量只能用于进程间同步,...

    s1mba
  • Working中常用ADB

    adb logcat -s blink或adb shell "getprop sys.btauthorised"

    用户6367961
  • BZOJ1191: [HNOI2006]超级英雄Hero(二分图匹配)

    现在电视台有一种节目叫做超级英雄,大概的流程就是每位选手到台上回答主持人的几个问题,然后根据回答问题的

    attack
  • 2018读什么?花了一周爬取豆瓣,我发现了这些

    大家平时怎么找书来看?很多人会从自己喜欢的书中提到的书目,跳到下一本书;读和自己喜欢的作家文风类似的作家的书;看看畅销书榜单...... 渐渐地你发现自己到达...

    kalifa_lau
  • 需求调研报告

    公众号php_pachong
  • MySQL配置文件及参数详解

    MySQL的配置文件需要根据版本及实际情况进行相应配置,本人使用的是Percona版本,主要是用到线程池等功能,所以选择Percona版本,配置文件内容如下,大...

    July
  • Java8集合源码解析-Hashtable源码剖析1 概述2 源码解析rehash方法3 总结

    公众号-JavaEdge
  • 以B端系统案例,解析需求调研5个方法

    对产品经理来说,绝大多数工作时间都是围绕需求展开的——需求分析、需求调研、跟进实现需求等。

    博文视点Broadview

扫码关注云+社区

领取腾讯云代金券