线程同步与条件变量

再论生产消费者问题

问题

如果缓冲池为空,如何定义消费者的行为?

一种可能的解决方案

这种方案是可行的,但是如果生产者一直不生产,那么消费者会反复查看产品的数量为 0 并休眠,这样会浪费 cpu 的资源,并且生产者生产后,消费者并不能马上从休眠状态中被唤醒去取走产品,这种方案的效率并不高

需求:

消费者发现缓冲池为空的时候,主动让出互斥量,并进入等待状态

当缓冲池非空,即: 生产者更新缓冲池状态, 消费者再次竞争锁

关键:

消费者不需要反复多次竞争锁 (缓冲池为空, 直接等待)

生产者可通知消费者再次竞争锁 (生产者与消费者协同机制)

Linux 中的条件变量

条件变量的注意事项

条件变量之间不能相互初始化,也不能相互赋值 (行为未定义)

使用 pthread_cond_init() 初始化的条件变量,必须被销毁,且可以重新初始化

使用 PTHREAD_COND_INITIALIZER 初始化的条件变量不需要销毁

不能使用 未初始化 / 已销毁 的条件变量 (行为未定义)

pthread_cond_wait() 只能由拥有互斥量的线程调用

条件变量的使用

条件变量初体验

test1.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <time.h>


int g_count = 0;
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;

void* customer_thread(void* arg)
{     
    while( 1 )
    {
        pthread_mutex_lock(&g_mutex);
        
        if( g_count > 0 )
            printf("%s : %d\n", __FUNCTION__, --g_count);        
        else
            pthread_cond_wait(&g_cond, &g_mutex); // g_cond points to the queue, g_mutex needs to be unlocked
            
        pthread_mutex_unlock(&g_mutex);
        
        usleep(500 * 1000);
    }
        
    return NULL;
}

int main()
{
    pthread_t t = 0;  
    int pre = 0;

    pthread_create(&t, NULL, customer_thread, NULL);
    
    printf("Hello World!\n");
    
    while( 1 )
    {
        pthread_mutex_lock(&g_mutex);
        
        pre = g_count;
        
        printf("%s : %d\n", __FUNCTION__, ++g_count);  
        
        if( pre == 0 )
        {
            printf("%s : %s\n", __FUNCTION__, "signal customer to get product...");
            pthread_cond_signal(&g_cond);
        }
        
        pthread_mutex_unlock(&g_mutex);
        
        usleep(2000 * 1000); 
    }
    
    printf("End!\n");
  
    return 0;
}

主线程为生产者,负责生产产品;子线程 customer_thread 为消费者,负责取走产品

第 24 行,如果消费者发现没有产品,则调用 pthread_cond_wait(&g_cond, &g_mutex) 阻塞在 g_cond 这个条件变量上,并释放已获取到的互斥锁 g_mutex

第 54 行,如果生产者生产了产品后发现上一次的产品没了,则调用 pthread_cond_signal(&g_cond),来唤醒阻塞在 g_cond 这个条件变量上的单个消费者线程来取产品,这样原先阻塞在 g_cond 这个条件变量上的某个线程就会被唤醒,重新抢夺互斥锁,取走产品,然后释放互斥锁

程序运行结果如下图所示:

进阶条件变量

int pthread_cond_signal(pthread_cond_t* cond);

  • 唤起一个等待目标条件变量的线程

int pthread_cond_broadcast(pthread_cond* cond);

  • 唤起所有等待目标条件变量的线程

一个生产者 vs 多个消费者

存在的问题

解决方案

条件变量深度实验

test2.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <time.h>


int g_count = 0;
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;

void* customer_thread(void* arg)
{   
    int i = (long)arg;  
    
    // prepare to get product
    {
        pthread_mutex_lock(&g_mutex);
        
        if( g_count == 0 )
        // while( g_count == 0 )
            pthread_cond_wait(&g_cond, &g_mutex);
            
        // get product from buffer
        printf("customer %d : %d\n", i, g_count);
        
        g_count--;
            
        pthread_mutex_unlock(&g_mutex);
    }
        
    return NULL;
}

int main()
{
    pthread_t t[10] = {0};
    int len = sizeof(t)/sizeof(*t);
    long i = 0;

    for(i=0; i<len; i++)
    {
        pthread_create(&t[i], NULL, customer_thread, (void*)i);
    }
    
    printf("Hello World!\n");
    
    sleep(1);
    
    {
    
        // prepare to make product
        pthread_mutex_lock(&g_mutex);
        
        int v = g_count;  
        
        g_count += 10;
        
        if( v == 0 )
            pthread_cond_signal(&g_cond); 
            // pthread_cond_broadcast(&g_cond);
        
        pthread_mutex_unlock(&g_mutex);
    }
    
    for(i=0; i<len; i++)
    {
        pthread_join(t[i], NULL);
    }
    
    printf("End!\n");
  
    return 0;
}

第 46 行,主线程创建 10 个消费者线程 customer_thread,随后主线程休眠 1s,由于产品数量为 0,10 个消费者线程会阻塞在 g_cond 这个条件变量上

第 63 行,主线程生产了 10 个产品后,通过 pthread_cond_signal 来通知阻塞在 g_cond 这个条件变量上的单个线程,这样就会导致 10 个被阻塞的线程只有 1 个线程被唤醒,取走产品,其他线程依旧被阻塞

程序运行结果如下图所示:

pthread_cond_signal(cond) 函数只会唤醒单个阻塞在 cond 条件变量上的单个线程,我们将这个函数替换为 pthread_cond_broadcast(cond),而这个函数会唤醒所有阻塞在 cond 条件变量上的所有线程,这样 10 个消费者线程都会被唤醒,然后取走产品

替换后的程序

test2.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <time.h>


int g_count = 0;
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;

void* customer_thread(void* arg)
{   
    int i = (long)arg;  
    
    // prepare to get product
    {
        pthread_mutex_lock(&g_mutex);
        
        if( g_count == 0 )
        // while( g_count == 0 )
            pthread_cond_wait(&g_cond, &g_mutex);
            
        // get product from buffer
        printf("customer %d : %d\n", i, g_count);
        
        g_count--;
            
        pthread_mutex_unlock(&g_mutex);
    }
        
    return NULL;
}

int main()
{
    pthread_t t[10] = {0};
    int len = sizeof(t)/sizeof(*t);
    long i = 0;

    for(i=0; i<len; i++)
    {
        pthread_create(&t[i], NULL, customer_thread, (void*)i);
    }
    
    printf("Hello World!\n");
    
    sleep(1);
    
    {
    
        // prepare to make product
        pthread_mutex_lock(&g_mutex);
        
        int v = g_count;  
        
        g_count += 10;
        
        if( v == 0 )
            // pthread_cond_signal(&g_cond); 
            pthread_cond_broadcast(&g_cond);
        
        pthread_mutex_unlock(&g_mutex);
    }
    
    for(i=0; i<len; i++)
    {
        pthread_join(t[i], NULL);
    }
    
    printf("End!\n");
  
    return 0;
}

程序运行结果如下图所示:

pthread_cond_broadcast() 使得 10 个消费者线程都被唤醒

test3.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <time.h>


int g_count = 0;
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;

void* customer_thread(void* arg)
{   
    int i = (long)arg;  
    
    // prepare to get product
    {
        pthread_mutex_lock(&g_mutex);
        
        if( g_count == 0 )
        // while( g_count == 0 )
            pthread_cond_wait(&g_cond, &g_mutex);
            
        // get product from buffer
        printf("customer %d : %d\n", i, g_count);
        
        g_count--;
            
        pthread_mutex_unlock(&g_mutex);
    }
        
    return NULL;
}

int main()
{
    pthread_t t[10] = {0};
    int len = sizeof(t)/sizeof(*t);
    long i = 0;

    for(i=0; i<len; i++)
    {
        pthread_create(&t[i], NULL, customer_thread, (void*)i);
    }
    
    printf("Hello World!\n");
    
    sleep(1);
    
    {
    
        // prepare to make product
        pthread_mutex_lock(&g_mutex);
        
        int v = g_count;  
        
        g_count += 5;
        
        if( v == 0 )
            pthread_cond_broadcast(&g_cond);
        
        pthread_mutex_unlock(&g_mutex);
    }
    
    for(i=0; i<len; i++)
    {
        pthread_join(t[i], NULL);
    }
    
    printf("End!\n");
  
    return 0;
}

生产者为 1 个,消费者为 10 个,生产者一次生产 5 个产品

首先,10 个生产者会阻塞在 g_cond 这个条件变量上,生产者生产了 5 个产品后,调用 pthread_cond_broadcast(),唤醒了阻塞在 g_cond 这个条件变量上的 10 个消费者线程,但是消费者线程被唤醒后没有再次进行产品数量判断,而是直接取走了产品,这样就会出现问题

程序运行结果如下图所示:

产品数量出现了负数,我们将 23 行的 if 改为 while 即可解决这个问题

替换后的程序

test3.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <time.h>


int g_count = 0;
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;

void* customer_thread(void* arg)
{   
    int i = (long)arg;  
    
    // prepare to get product
    {
        pthread_mutex_lock(&g_mutex);
        
        while( g_count == 0 )
            pthread_cond_wait(&g_cond, &g_mutex);
            
        // get product from buffer
        printf("customer %d : %d\n", i, g_count);
        
        g_count--;
            
        pthread_mutex_unlock(&g_mutex);
    }
        
    return NULL;
}

int main()
{
    pthread_t t[10] = {0};
    int len = sizeof(t)/sizeof(*t);
    long i = 0;

    for(i=0; i<len; i++)
    {
        pthread_create(&t[i], NULL, customer_thread, (void*)i);
    }
    
    printf("Hello World!\n");
    
    sleep(1);
    
    {
    
        // prepare to make product
        pthread_mutex_lock(&g_mutex);
        
        int v = g_count;  
        
        g_count += 5;
        
        if( v == 0 )
            pthread_cond_broadcast(&g_cond);
        
        pthread_mutex_unlock(&g_mutex);
    }
    
    for(i=0; i<len; i++)
    {
        pthread_join(t[i], NULL);
    }
    
    printf("End!\n");
  
    return 0;
}

 程序运行结果如下图所示:

只有 5 个消费者线程取走了产品

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/588652.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MWCNTs微纳米纤维膜的制备

MWCNTs微纳米纤维膜是一种由多壁碳纳米管&#xff08;MWCNTs&#xff09;与聚合物纤维复合而成的纳米纤维膜。这种材料结合了MWCNTs的良好性能和纳米纤维膜的高比表面积、高通量等特点&#xff0c;具有广泛的应用前景。 在制备过程中&#xff0c;首先需要对MWCNTs进行酸化处理和…

基于SpringBoot的私人健身与教练预约管理系统设计与实现

一、引言 私人健身与教练预约管理系统&#xff0c;可以摆脱传统手写记录的管理模式。利用计算机系统&#xff0c;进行用户信息、管理员信息的管理&#xff0c;其中包含首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;教练管理&#xff0c;健身项目管理&#xff0c;项…

Agent AI智能体:如何借助机器学习引领科技新潮流

文章目录 &#x1f4d1;前言一、Agent AI智能体的基本概念二、Agent AI智能体的技术进步2.1 机器学习技术2.2 自适应技术2.3 分布式计算与云计算 三、Agent AI智能体的知识积累3.1 知识图谱3.2 迁移学习 四、Agent AI智能体的挑战与机遇4.1 挑战4.2 机遇 小结 &#x1f4d1;前言…

Linux下安装snaphu

1、官网下载安装包 2、解压&#xff0c;移动文件夹到/usr/local/下 3、在/usr/local/下创建man&#xff0c;在man下创建man1文件夹 4、进入到snaphu的src文件夹里&#xff0c;执行sudo make&#xff0c;如果报错 在这个 Makefile 中&#xff0c;-arch x86_64 是 macOS 特定的…

小区服务|基于SprinBoot+vue的小区服务管理系统(源码+数据库+文档)

目录 基于SprinBootvue的小区服务管理系统 一、前言 二、系统设计 三、系统功能设计 1管理员登录 2 客服聊天管理、反馈管理管理 3 公告信息管理 4公告类型管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博…

图论之最短路算法模板总结

来个大致的分类&#xff1a; 朴素的迪杰斯特拉&#xff1a; 实现&#xff1a; 我们让s表示当前已经确定的最短距离的点&#xff0c;我们找到一个不在s中的距离最近的点t&#xff0c;并用t来更新其他的点。 下面是AC代码&#xff1a; #include<bits/stdc.h> using nam…

《Spring-MVC》系列文章目录

简介 Spring MVC是一种基于Java的实现MVC设计模式的请求驱动类型的轻量级Web框架&#xff0c;它通过把Model&#xff08;模型&#xff09;、View&#xff08;视图&#xff09;、Controller&#xff08;控制器&#xff09;分离&#xff0c;将web层进行职责解耦&#xff0c;把复杂…

Getting started - 英文版 - English Version

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace This pa…

在不同操作系统上自动生成Protocol Buffers的Java语言包的方法

各语言的Protocol Buffers文件都需要通过protoc来生成&#xff0c;这个动作往往需要手动输入命令完成。本文介绍的方法&#xff0c;将借助Maven来实现自动化生成工作。这样开发者只要专注于proto的定义&#xff0c;且不用将生成的文件上传到代码仓库&#xff0c;从而降低开发的…

Tracecat:开源 SOAR

Tracecat 是一个面向安全团队的开源自动化平台。 开发人员认为&#xff0c;每个人都应该可以使用安全自动化&#xff0c;特别是人手不足的中小型团队。 核心功能、用户界面和日常工作流程基于一流安全团队的现有最佳实践。 使用专门的人工智能模型来标记、总结和丰富警报。 …

【软件开发规范篇】JAVA后端开发编码注释规范

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

20240502在WIN11下显示桌面

20240502在WIN11下显示桌面 2024/5/2 15:06 百度&#xff1a;WIN11 状态栏 右键 显示桌面 在WIN10下&#xff0c;可以在状态栏点击右键→“显示桌面”来最小化全部窗口&#xff0c;特别是我打开的浏览器的巨多的窗口&#xff01; 但是在WIN11下&#xff0c;这个【显示桌面】怎…

2024五一杯数学建模B题思路代码文章教学-交通需求规划与可达率问题

交通需求规划与可达率问题 问题总结&#xff1a; 问题一&#xff1a;在一个小型交通网络中&#xff0c;给定的起点和终点之间的交通需求需分配到相应路径上。目标是最大化任意一条路段出现突发状况时的交通需求期望可达率。 问题二&#xff1a;在一个较大的交通网络中&#xff…

Linux-进程调度器

1. 前言 在计算机中&#xff0c;进程的数量远多于cpu的数量&#xff0c;所以就存在&#xff0c;多个进程抢占一个cpu的情况&#xff0c;所以就需要一套规则&#xff0c;决定这些进程被处理的顺序&#xff0c;这就叫做进程调度。 在我的简单理解下&#xff0c;其实就是把进程放…

普乐蛙景区vr体验馆VR游乐场设备身历其境体验

小编给大家推荐一款gao坪效产品【暗黑战车】&#xff0c;一次6人同乘&#xff0c;炫酷外观、强大性能和丰富内容适合各个年龄层客群&#xff0c;紧张刺激的VR体验让玩家沉浸在元宇宙的魅力中&#xff0c;无论是节假日还是平日&#xff0c;景区商场助力门店提高客流量和营收~ ◆…

实验三 .Java 语言继承和多态应用练习 (课内实验)

一、实验目的 本次实验的主要目的是通过查看程序的运行结果及实际编写程序&#xff0c;练习使用 Java 语言的继承特性。 二、实验要求 1. 认真阅读实验内容&#xff0c;完成实验内容所设的题目 2. 能够应用多种编辑环境编写 JAVA 语言源程序 3. 认真体会多态与继承的作用…

B+树详解与实现

B树详解与实现 一、引言二、B树的定义三、B树的插入四、B树的删除五、B树的查找效率六、B树与B树的区别和联系 一、引言 B树是一种树数据结构&#xff0c;通常用于数据库和操作系统的文件系统中。它的特点是能够保持数据稳定有序&#xff0c;其插入与修改拥有较稳定的对数时间…

WebGL/Cesium 大空间相机抖动 RTE(Relative to Eye)实现原理简析

在浏览器中渲染大尺寸 3D 模型&#xff1a;Speckle 处理空间抖动的方法 WebGL/Cesium 大空间相机抖动 RTE(Relative to Eye)实现原理简析 注: 相机空间和视图空间 概念等效混用 1、实现的关键代码 const material new THREE.RawShaderMaterial({uniforms: {cameraPostion: {…

【Qt QML】用CMake管理Qt工程

CMake是一个开源、跨平台的工具系列&#xff0c;用于构建、测试和打包软件。CMake使用简单的独立配置文件来控制软件编译过程。与许多跨平台系统不同&#xff0c;CMake被设计为与本地构建环境结合使用。 下面我们在CMake项目中使用Qt的最基本方法。首先&#xff0c;创建一个基本…

如何解决pycharm创建项目报错 Error occurred when installing package ‘requests‘. Details.

&#x1f42f; 如何解决PyCharm创建项目时的包安装错误&#xff1a;‘requests’ &#x1f6e0;️ 文章目录 &#x1f42f; 如何解决PyCharm创建项目时的包安装错误&#xff1a;requests &#x1f6e0;️摘要引言正文&#x1f4d8; **问题分析**&#x1f680; **更换Python版本…