《OSTEP》条件变量(chap30)

2023-11-11 22:36
文章标签 条件 变量 ostep chap30

本文主要是介绍《OSTEP》条件变量(chap30),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

〇、前言

本文是对《OSTEP》第三十章的实践与总结。

一、条件变量

#include <pthread.h>
#include <stdio.h>
#include <assert.h>int buffer;
int count = 0; // 资源为空// 生产,在 buffer 中放入一个值
void put(int value) {assert(count == 0);count = 1;buffer = value;
}
// 消费,取出 buffer 中的值
int get() {assert(count == 1);count = 0;return buffer;
}/***********第一版本**********/
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {put(i);}return NULL;
}
// 消费者
void *consumer(void *arg) {while (1) {int temp = get();printf("消费的值:%d\n", temp);}return NULL;
}int main() {pthread_t p1, p2;int arg = 100;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, NULL);// 等待两个线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);return 0;
}

运行:

*** chap30_条件变量 % gcc -o a con_prodece.c
*** chap30_条件变量 % ./a
Assertion failed: (count == 0), function put, file con_prodece.c, line 10.
消费的值:0
Assertion failed: (count == 1), function get, file con_prodece.c, line 16.
zsh: abort      ./a

可以看到,断言直接失败。

pthread_cond_t cond;
pthread_mutex_t mutex;
/***********第二版本**********/
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);if (count == 1) {pthread_cond_wait(&cond, &mutex);}put(i);pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);}return NULL;
}
// 消费者
void *consumer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);if (count == 0) {pthread_cond_wait(&cond, &mutex);}int temp = get();pthread_mutex_unlock(&mutex);printf("消费:%d\n", temp);pthread_cond_signal(&cond); }return NULL;
}int main() {// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_t p1, p2;int arg = 100;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, &arg);// 等待两个线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

运行结果:

*** chap30_条件变量 % ./a                    
消费:0
消费:1
消费:2
消费:3
...
消费:95
消费:96
消费:97
消费:98
消费:99

可以看到,在两个线程的情况下,工作的很好。

/***********第三版本**********/
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);if (count == 1) {pthread_cond_wait(&cond, &mutex);}put(i);pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);}return NULL;
}
// 消费者
void *consumer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);if (count == 0) {pthread_cond_wait(&cond, &mutex);}int temp = get();pthread_mutex_unlock(&mutex);printf("消费:%d\n", temp);pthread_cond_signal(&cond); }return NULL;
}int main() {// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_t p1, p2, p3;int arg = 100;int arg1 = 50;int arg2 = 50;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, &arg1);pthread_create(&p3, NULL, consumer, &arg2);// 等待两个线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);pthread_join(p3, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

运行结果:

*** chap30_条件变量 % gcc -o a con_prodece2.c
*** chap30_条件变量 % ./a                    
消费:0
消费:1
Assertion failed: (count == 1), function get, file con_prodece2.c, line 18.
zsh: abort      ./a

可以看到,再增加了一个消费线程之后,出现了断言错误。这是因为出现了假唤醒,使得某个线程醒来后,断言错误。解决方法很简单,直接将 if()换成 while():

/***********第三版本**********/
// 解决虚假唤醒
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);while (count == 1) {pthread_cond_wait(&cond, &mutex);}put(i);pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);}return NULL;
}
// 消费者
void *consumer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond, &mutex);}int temp = get();pthread_mutex_unlock(&mutex);printf("消费:%d\n", temp);pthread_cond_signal(&cond); // 在解锁之后发出信号}return NULL;
}int main() {// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_t p1, p2, p3;int arg = 100;int arg1 = 50;int arg2 = 50;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, &arg1);pthread_create(&p3, NULL, consumer, &arg2);// 等待两个线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);pthread_join(p3, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

运行结果:

luliang@shenjian chap30_条件变量 % ./a
消费:0
消费:2
消费:1
消费:3
消费:4
消费:5
...

可以看到会卡住,这其实是由于三个线程都睡眠导致的,这种情况是怎么发生的呢?
假设生产者唤醒了第一个消费者,消费者又恰巧唤醒了第二个生产者,第二个生产者被唤醒之后又睡眠。这样三个线程都在睡眠。解决问题的办法就是消费者只能唤醒生产者,生产者只能唤醒消费者。以下就是终极版本的代码:

#include <assert.h>
#include <pthread.h>
#include <stdio.h>int buffer;
int count = 0; // 资源为空
pthread_cond_t cond_consumer;
pthread_cond_t cond_procedure;
pthread_mutex_t mutex;// 生产,在 buffer 中放入一个值
void put(int value) {assert(count == 0);count = 1;buffer = value;
}
// 消费,取出 buffer 中的值
int get() {assert(count == 1);count = 0;return buffer;
}/***********第四版本**********/
// 假设生产者唤醒了第一个消费者,消费者又唤醒了第二个生产者,第二个生产者
// 之后又睡眠.这样三个线程都在睡眠.
// 核心问题就是,消费者只能唤醒生产者,生产者只能唤醒消费者.
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);while (count == 1) {pthread_cond_wait(&cond_procedure, &mutex);}put(i);pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond_consumer);}return NULL;
}
// 消费者
void *consumer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond_consumer, &mutex);}int temp = get();pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond_procedure);printf("消费:%d\n", temp);}return NULL;
}int main() {// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond_consumer, NULL);pthread_cond_init(&cond_procedure, NULL);pthread_t p1, p2, p3;int arg = 100;int arg1 = 50;int arg2 = 50;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, &arg1);pthread_create(&p3, NULL, consumer, &arg2);// 等待线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);pthread_join(p3, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond_consumer);pthread_cond_destroy(&cond_procedure);return 0;
}

运行结果:

*** chap30_条件变量 % gcc -o a con_prodece3.c
*** chap30_条件变量 % ./a
消费:0
消费:2
消费:1
...
消费:97
消费:98
消费:99

可以看到运行得很好,成功地解决了并发、虚假唤醒以及全部线程都睡眠的情况。

二、总结

我们看到了引入锁之外的另一个重要同步原语:条件变量。当某些程序状态不符合要求时,通过允许线程进入休眠状态,条件变量使我们能够漂亮地解决许多重要的同步问题,包括著名的(仍然重要的)生产者/消费者问题,以及覆盖条件。

这篇关于《OSTEP》条件变量(chap30)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/393277

相关文章

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

详解如何在React中执行条件渲染

《详解如何在React中执行条件渲染》在现代Web开发中,React作为一种流行的JavaScript库,为开发者提供了一种高效构建用户界面的方式,条件渲染是React中的一个关键概念,本文将深入探讨... 目录引言什么是条件渲染?基础示例使用逻辑与运算符(&&)使用条件语句列表中的条件渲染总结引言在现代

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

java如何调用kettle设置变量和参数

《java如何调用kettle设置变量和参数》文章简要介绍了如何在Java中调用Kettle,并重点讨论了变量和参数的区别,以及在Java代码中如何正确设置和使用这些变量,避免覆盖Kettle中已设置... 目录Java调用kettle设置变量和参数java代码中变量会覆盖kettle里面设置的变量总结ja

Perl 特殊变量详解

《Perl特殊变量详解》Perl语言中包含了许多特殊变量,这些变量在Perl程序的执行过程中扮演着重要的角色,:本文主要介绍Perl特殊变量,需要的朋友可以参考下... perl 特殊变量Perl 语言中包含了许多特殊变量,这些变量在 Perl 程序的执行过程中扮演着重要的角色。特殊变量通常用于存储程序的

Python按条件批量删除TXT文件行工具

《Python按条件批量删除TXT文件行工具》这篇文章主要为大家详细介绍了Python如何实现按条件批量删除TXT文件中行的工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.简介2.运行效果3.相关源码1.简介一个由python编写android的可根据TXT文件按条件批

变量与命名

引言         在前两个课时中,我们已经了解了 Python 程序的基本结构,学习了如何正确地使用缩进来组织代码,并且知道了注释的重要性。现在我们将进一步深入到 Python 编程的核心——变量与命名。变量是我们存储数据的主要方式,而合理的命名则有助于提高代码的可读性和可维护性。 变量的概念与使用         在 Python 中,变量是一种用来存储数据值的标识符。创建变量很简单,

封装MySQL操作时Where条件语句的组织

在对数据库进行封装的过程中,条件语句应该是相对难以处理的,毕竟条件语句太过于多样性。 条件语句大致分为以下几种: 1、单一条件,比如:where id = 1; 2、多个条件,相互间关系统一。比如:where id > 10 and age > 20 and score < 60; 3、多个条件,相互间关系不统一。比如:where (id > 10 OR age > 20) AND sco

JS_变量

二、JS的变量 JS中的变量具有如下特征 1 弱类型变量,可以统一声明成var 2 var声明的变量可以再次声明 3 变量可以使用不同的数据类型多次赋值 4 JS的语句可以以; 结尾,也可以不用;结尾 5 变量标识符严格区分大小写 6 标识符的命名规则参照JAVA 7 如果使用了 一个没有声明的变量,那么运行时会报uncaught ReferenceError: *** is not de

使用条件变量实现线程同步:C++实战指南

使用条件变量实现线程同步:C++实战指南 在多线程编程中,线程同步是确保程序正确性和稳定性的关键。条件变量(condition variable)是一种强大的同步原语,用于在线程之间进行协调,避免数据竞争和死锁。本文将详细介绍如何在C++中使用条件变量实现线程同步,并提供完整的代码示例和详细的解释。 什么是条件变量? 条件变量是一种同步机制,允许线程在某个条件满足之前进入等待状态,并在条件满