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

相关文章

来自专栏Golang语言社区

Golang语言--【基础知识】访问数据库

对许多Web应用程序而言,数据库都是其核心所在. 数据库几乎可以用来存储你想查询和修改的任何信息,比如用户信息、产品目录或者新闻列表等。 Go没有内置的驱动支持...

4496
来自专栏漫漫全栈路

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

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

4015
来自专栏黑白安全

初涉web安全

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

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

TestNG的简单使用

5767
来自专栏木木玲

Netty 源码解析 ——— Netty 优雅关闭流程

4463
来自专栏决胜机器学习

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

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

3796
来自专栏服务端技术杂谈

从org.springframework.dao.DuplicateKeyException说起

通常在dao层将所有异常转嫁到Spring的RuntimeException体系(org.springframework.dao.DataAccessExcep...

2644
来自专栏黑泽君的专栏

day57_BOS项目_09

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

912
来自专栏JavaQ

Java中锁概念总结

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

3065
来自专栏Linux驱动

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

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

2788

扫码关注云+社区

领取腾讯云代金券