linux用户态条件变量和内核态完成变量

如果我们的线程要等一个条件满足之后才可以继续向下执行,这个条件不满足的话,就要等待这个条件。这种场景经常见到,比如我们使用recv接收网络数据的时候,或者使用epoll_wait来等待事件的时候,在默认情况下,recv和epoll_wait都会等到有数据或者事件到来才会返回。

生产者和消费者的场景是条件变量典型的应用场景。一个队列,一个生产者,一个消费者,生产者向队列中生产数据,消费者从队列中消费数据。当队列满的时候,生产者就不能继续生产了,需要等有元素被消费之后才能继续生产;当队列空的时候,消费者不能继续消费数据了,需要有新的数据被生产才能继续消费。

具体怎么等待条件呢?有两种基本的方式:

轮询:

比如生产者在队列满的时候,通过轮询的方式不断地去确定队列是不是满,如果满,则继续等待;否则就继续生产数据。

条件:

当队列满的时候,生产者就睡眠,消费者消费数据之后,将生产者唤醒。

轮询和条件是两种基本的实现方式,这两种思想应用于很多场景,比如硬件和软件的通信方式:轮询和中断,中断就类似于这里的条件;比如多线程中使用的自旋锁和互斥体,前者是轮询的方式,后者是条件的方式。

本文讨论条件的方式。在linux中用户态和内核态均提供了这样的机制:用户态的条件变量,内核态的完成变量。

1用户态:条件变量

1.1使用

(1)条件变量要和互斥体mutex在一块使用

(2)wait的时候有两点需要注意

①wait之前要加锁,wait的时候会释放锁,wait返回之前会再次加锁。也就是说在函数pthread_cond_wait中,会有一次解锁和加锁的过程。

②wait返回之后要再次判断条件是不是满足,本例中消费者和生产者只有一个,而在实际使用中,生产者和消费者可能有多个。如果唤醒侧调用了pthread_cond_broadcast,那么等待者就会全部被唤醒,这样可能数据已经被先唤醒的线程消费了,后被唤醒的线程没有数据消费,所以需要加判断。这是条件变量的典型用法。

(3)wait侧使用方式

加锁

wait

解锁

(4)唤醒侧使用方式

加锁

signal/broadcast

解锁

唤醒侧其实也可以不加锁,也能起到唤醒的作用,如下man手册中所说。但是如果要求行为是可预知的,那么最好加锁。在实际应用中,我们当然需要代码的行为是可预知的,所以我们在使用中要加锁。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>#define BUFFER_SIZE 5int buffer[BUFFER_SIZE];
int count = 0;pthread_mutex_t mutex;
pthread_cond_t cond_producer;
pthread_cond_t cond_consumer;void* producer(void* arg) {for (int i = 0; i < 10; i++) {pthread_mutex_lock(&mutex);while (count == BUFFER_SIZE) {pthread_cond_wait(&cond_producer, &mutex);}buffer[count] = i;count++;printf("Produced: %d\n", i);pthread_cond_signal(&cond_consumer);pthread_mutex_unlock(&mutex);sleep(1);}return NULL;
}void* consumer(void* arg) {for (int i = 0; i < 10; i++) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond_consumer, &mutex);}count--;int item = buffer[count];printf("Consumed: %d\n", item);pthread_cond_signal(&cond_producer);pthread_mutex_unlock(&mutex);sleep(2);}return NULL;
}int main() {pthread_t prod_thread, cons_thread;pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond_producer, NULL);pthread_cond_init(&cond_consumer, NULL);pthread_create(&prod_thread, NULL, producer, NULL);pthread_create(&cons_thread, NULL, consumer, NULL);pthread_join(prod_thread, NULL);pthread_join(cons_thread, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond_producer);pthread_cond_destroy(&cond_consumer);return 0;
}

1.2惊群问题

惊群问题说的是,如果有多个线程wait阻塞,这个时候一个线程调用signal,可能会唤醒多个线程。如下man手册中也说的是,pthread_cond_signal会唤醒至少一个线程,也就是说至少一个,也可能大于一个,大于一个就是惊群(你只扔了一个米粒,导致鸡群都跑了过来,但是只有一只鸡能够吃到米粒,其它的米都白跑一趟,惊动集群)

本人在在linux环境下测试,从来没有出现过惊群问题。即使有惊群问题,通过如下典型的使用方式,在wait返回之后再次判断,也能避免惊群问题引入其它问题。

while (count == 0) {pthread_cond_wait(&cond_consumer, &mutex);
}

1.3destroy无法返回问题

如果一个条件变量,有线程在wait这个条件变量。这个时候调用pthread_cond_destroy销毁条件变量,那么pthread_cond_destroy是会阻塞住,直到pthread_cond_wait返回之后,destroy才会成功。

在实际应用中,在类的析构函数里,或者进程的main函数返回时,需要销毁资源,往往会调用pthread_cond_destroy销毁条件变量,此时就要注意是不是有线程在wait。如果遇到destroy不返回的情况,要排查是不是有线程wait的情况。也可以使用pthread_cond_timedwait这个api,这个api不会一直等待,如果条件不满足,超时的时候,也会返回。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mutex;
pthread_cond_t cond_var;void *wait_thread(void *arg) {pthread_mutex_lock(&mutex);struct timespec ts;clock_gettime(CLOCK_REALTIME, &ts);ts.tv_sec += 5;pthread_cond_timedwait(&cond_var, &mutex, &ts);// pthread_cond_wait(&cond_var, &mutex);printf("Wait thread resumed.\n");pthread_mutex_unlock(&mutex);return NULL;
}void *destroy_thread(void *arg) {printf("before destroy condition.\n");pthread_cond_destroy(&cond_var);printf("after destroy condition.\n");return NULL;
}int main() {pthread_t wait_tid, destroy_tid;pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond_var, NULL);pthread_create(&wait_tid, NULL, wait_thread, NULL);sleep(1);pthread_create(&destroy_tid, NULL, destroy_thread, NULL);pthread_join(wait_tid, NULL);pthread_join(destroy_tid, NULL);pthread_mutex_destroy(&mutex);return 0;
}

2内核态:完成变量

如下是内核完成变量使用的例子。

struct completion声明一个完成变量,init_completion初始化完成变量,wait_for_completion等待完成,complete唤醒等待线程。另外,complete_all可以唤醒所有的等待线程。

用户态条件变量内核态完成变量
pthread_cont_tstruct completion
pthread_cond_initinit_completion
pthread_cond_waitwait_for_completion
pthread_cond_signalcomplete
pthread_cond_broadcastcomplete_all

源码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/kthread.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple completion example module");static struct completion my_completion;
static struct task_struct *my_thread;static int my_thread_fn(void *data)
{printk(KERN_INFO "Thread is sleeping for 5 seconds...\n");ssleep(5);printk(KERN_INFO "Thread is completing...\n");complete(&my_completion);return 0;
}static int __init my_module_init(void)
{printk(KERN_INFO "Loading my completion module...\n");init_completion(&my_completion);my_thread = kthread_run(my_thread_fn, NULL, "my_thread");if (IS_ERR(my_thread)) {printk(KERN_ERR "Failed to create thread\n");return PTR_ERR(my_thread);}wait_for_completion(&my_completion);printk(KERN_INFO "Thread has completed!\n");return 0;
}static void __exit my_module_exit(void)
{printk(KERN_INFO "Unloading my completion module...\n");
}module_init(my_module_init);
module_exit(my_module_exit);

Makefile:

obj-m += comp.o
all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

双指针 — 复写零

目录 1. 题目解析 2. 算法讲解 1.算法原理 2.“异地”操作 3.“就地”操作 误解 正解 从后往前完成复写操作 找到最后一个复写的数 处理边界情况 3. 编写代码 正解顺序 1.找到最后一个复写的数 2.处理边界情况 3.从后往前完成复写操作 性能分析&#xff1a; …

【白话文通俗易懂搞明白并解决】跨域问题

文章目录 跨域出现的场景跨域的定义解决跨域的方法方法一&#xff1a;JSONP方法二&#xff1a;添加响应头方法三&#xff1a;通过nginx代理跨域 跨域过滤器代码示例 跨域出现的场景 在前后端分离项目中&#xff0c;经常会出现跨域问题&#xff0c;表现为&#xff1a; 当在浏览…

拟声 0.37.0 | 拟物风格,超级优美,功能丰富

拟声是一款功能丰富的音视频播放器&#xff0c;支持多种音频来源&#xff0c;并具备独特的歌词弹幕、音源转换、跨设备共享与控制等功能。其创新的LRC歌词编解码器和新拟物风格的UI设计为用户提供了一个全新的视听体验。 大小&#xff1a;36M 百度网盘&#xff1a;https://pan…

7.存储过程中的事务管理(7/10)

1.引言 在现代信息技术快速发展的今天&#xff0c;数据库已经成为存储和管理数据的核心工具。无论是企业级应用、电子商务平台还是个人项目&#xff0c;数据库都扮演着不可或缺的角色。在这些应用中&#xff0c;数据的完整性、一致性和可靠性是至关重要的。这就引出了数据库事…

vue3--通用组件 popup 封装

在业务场景中,假设这里我们要实现点击 汉堡 后,会有一个自下而上的popup弹出层 因此这里我们需要先实现这样的一个公共的popup弹出层 那么我们这里的popup弹出层需要具备以下能力: 当popup展开时,内容视图应该不属于任何一个组件内部,而应该直接被插入到body下,这里需要…

【C++11】可变模板参数详解

个人主页&#xff1a;chian-ocean 文章专栏 C 可变模板参数详解 1. 引言 C模板是现代C编程中一个非常强大且灵活的工具。在C11标准中&#xff0c;引入了可变模板参数&#xff08;variadic templates&#xff09;&#xff0c;它为模板编程带来了革命性改变。它的出现允许我们…

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,1-8

文件下载与邀请翻译者 学习英特尔开发手册&#xff0c;最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册&#xff0c;会是一件耗时费力的工作。如果有愿意和我一起来做这件事的&#xff0c;那么&#xff…

FlinkCDC 实现 MySQL 数据变更实时同步

文章目录 1、基本介绍2、代码实战2.1、数据源准备2.2、代码实战2.3、数据格式 1、基本介绍 Flink CDC 是 Apache Flink 提供的一个功能强大的组件&#xff0c;用于实时捕获和处理数据库中的数据变更。可以实时地从各种数据库&#xff08;如MySQL、PostgreSQL、Oracle、MongoDB…

【云岚到家】-day07-5-实战项目-优惠券活动-活动管理

【云岚到家】-day07-5-实战项目-优惠券活动-活动管理 2 优惠券活动管理2.1 需求分析2.1.1 **新增优惠券活动**1&#xff09;界面原型2&#xff09;数据分析3&#xff09;数据校验 2.1.2 **查询优惠券活动**1&#xff09;界面原型 2.2.3 **修改优惠券活动**1) 界面原型2&#xf…

Qt-窗口对话框QMessageBox的使用(51)

目录 前言 描述 使用 自定义按钮 简单方式创建 前言 Qt 提供了多种可复⽤的对话框类型&#xff0c;即 Qt 标准对话框。Qt 标准对话框全部继承于 QDialog类。常⽤标准对话框如下&#xff1a; 描述 消息对话框 QMessageBox 消息对话框是应⽤程序中最常⽤的界⾯元素。消息…

D3.js(五):实现组织架构图

实现组织架构图 效果初始化组织机构容器并实现缩放平移功能效果源码 渲染节点效果源码 渲染连线效果源码 完整源码 效果 初始化组织机构容器并实现缩放平移功能 效果 源码 import {useEffect} from react; import TreeData from ./json/tree-data.json;interface ITreeConfig…

crd介绍

在 Kubernetes 中&#xff0c;CRD&#xff08;Custom Resource Definition&#xff09;和 CR&#xff08;Custom Resource&#xff09;是用于扩展 Kubernetes 功能的机制。它们的关系和使用可以用一个完整案例来说明。 定义 CRD&#xff08;Custom Resource Definition&#x…

中后台 B 端产品设计

中后台 B 端产品设计 一、设计目标二、设计流程三、设计要点四、相关模块 叮嘟&#xff01;这里是小啊呜的学习课程资料整理。好记性不如烂笔头&#xff0c;今天也是努力进步的一天。一起加油进阶吧&#xff01; 中后台B端产品设计&#xff1a; 是指针对企业内部业务人员和管理…

python+appium+雷电模拟器安卓自动化及踩坑

一、环境安装 环境&#xff1a;window11 1.1 安装Android SDK AndroidDevTools - Android开发工具 Android SDK下载 Android Studio下载 Gradle下载 SDK Tools下载 这里面任选一个就可以&#xff0c;最终下载完主要要安装操作安卓的工具adb&#xff0c;安装这个步骤的前提是要…

Linux驱动开发——设备树

文章目录 1 什么是设备树&#xff1f;2 DTS、DTB和DTC3 DTS语法3.1 dtsi头文件3.2 设备节点3.3 标准属性3.4 根节点compatible属性3.5 向节点追加或修改内容 4 创建小型模板设备树5 设备树在系统中的体现6 绑定信息文档7 设备树常用OF操作函数7.1 查找节点的OF函数7.2 查找父/子…

【工具变量】上市公司当年是否发生财务重述指标整理Stata代码(2000-2023年)

计算说明&#xff1a;使用财务重述公告中所更正年报对应的年度作为财务重述的年度&#xff0c;若企业年报中发生财务重述取1&#xff0c;否则取0。本示例的财务重述是指上市公司对以前年度财务报表中的会计差错进行更正和披露&#xff0c;不包括股票拆分、股票红利、终止经营、…

Java 类和对象详解(上 )

个人主页&#xff1a; 鲤鱼王打挺-CSDN博客 Java专栏&#xff1a;https://blog.csdn.net/2401_83779763/category_12801101.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12801101&sharereferPC&sharesource2401_83779763&sharefromfrom_link &…

SwiftUI 如何取得 @Environment 中 @Observable 对象的绑定?

概述 从 SwiftUI 5.0&#xff08;iOS 17&#xff09;开始&#xff0c;苹果推出了全新的 Observation 框架。它作为下一代内容改变响应者全面参与到数据流和事件流的系统中。 有了 Observation 框架的加持&#xff0c;原本需要多种状态类型的 SwiftUI 视图现在只需要 3 种即可大…

R语言详解predict函数

R语言中predict函数在建立模型&#xff0c;研究关系时常用。但是不同type得到的结果常常被混为一谈&#xff0c;接下来&#xff0c;探讨predict得到的不同结果。 #数据 set.seed(123) n<-1000 age<-rnorm(n,mean50,sd10) gender<-rbinom(n,1,0.5) disease<-rbinom…

CDC变更数据捕捉技术是什么?和ETL有什么不同?

一、什么是CDC技术? 变更数据捕获&#xff08;Change Data Capture&#xff0c;简称 CDC&#xff09;是一种用于识别和跟踪数据源中发生变化的数据的技术。 工作原理&#xff1a; 1.监测数据源&#xff1a;CDC 工具会持续监测指定的数据源&#xff0c;如数据库表、文件系统…