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 条评论
登录 后参与评论

相关文章

来自专栏决胜机器学习

PHP开发过程的那些坑(四) ——PDO bindParam函数

PHP开发过程的那些坑(四)——PDO bindParam函数 (原创内容,转载请注明来源,谢谢) 坑: bindParam是PDOStatement的一个方法...

3166
来自专栏JavaQ

Java中锁概念总结

Java开发过程中会涉及很多锁,这些锁的作用各不相同,本篇对这些锁的概念及作用进行了整理。 公平锁和非公平锁 公平锁:多个线程申请获取同一个锁,按照线程的申请顺...

2755
来自专栏chenssy

【死磕Sharding-jdbc】---基于 SSM 集成sharding-jdbc2.0.3

本篇文章讲解如何在ssm(spring、springmvc、mybatis)结构的程序上集成sharding-jdbc(版本为2.0.3)进行分库分表; 假设分...

661
来自专栏Linux驱动

36.Linux驱动调试-根据oops定位错误代码行

1.当驱动有误时,比如,访问的内存地址是非法的,便会打印一大串的oops出来 1.1以LED驱动为例 将open()函数里的ioremap()屏蔽掉,直接使用物...

2298
来自专栏一枝花算不算浪漫

TestNG的简单使用

1292
来自专栏漫漫全栈路

JSP Cheat Sheet - JavaWeb基础学习速查表

JavaWeb学习速查表 整理JavaWeb学习中遇到的问题以及笔记,按分类编排 JSP基本语法 指令标识 - 脚本标识 - JSP注释 - 动作标识 JSP内...

3765
来自专栏凉城

[教程]黑客级别的批量处理文件

2055
来自专栏黑泽君的专栏

day57_BOS项目_09

方式一:   第一步:获得activiti-eclipse-plugin.zip文件   第二步:将zip文件解压到eclipse的dropins目录中

672
来自专栏黑白安全

初涉web安全

最近看了些web安全方面的文章,略有心得,写这篇文章来整理下思路,如有错误,恳请斧正

1315
来自专栏c#开发者

Asp.net webform scaffolding结合Generic Unit of Work & (Extensible) Repositories Framework代码生成向导

Asp.net webform scaffolding结合Generic Unit of Work & (Extensible) Repositories Fr...

2825

扫码关注云+社区