Modern C++ 条件变量

今天无意中看到一篇帖子,关于条件变量的,不过仔细看看发现它并达不到原本的目的。

程序如下,读者可以先想想他的本意,以及有没有问题:

#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <unistd.h>
using namespace std;
//全局条件变量
condition_variable cond;
mutex _mutex;
int count = 0;
 
void fun1(){
    while(1)
    {
        count++;
        unique_lock<mutex>lock(_mutex);
        if(count%5 == 0)
        {
            cond.notify_one();
        }
        else
        {
            cout<<"this is fun1,count="<<count<<endl;
        }
        lock.unlock();
        sleep(1);
    }
}
 
void fun2()
{
    while(1)
    {
        unique_lock<mutex>lock(_mutex);
        cond.wait(lock);
        cout<<"this is fun2,count="<<count<<endl;
        lock.unlock();
        sleep(2);
    }
}
 
int main()
{
    thread t1(fun1);
    thread t2(fun2);
    t1.join();
    t2.join();
    return 0;
}

OK,本意显然是:

  1. 从1开始打印整数
  2. 线程t1, 打印非5的倍数
  3. 线程t2, 打印5的倍数

编译执行,运行的还不错,符合预期,但这都是sleep的功劳。把fun1中的sleep去掉,fun2中的sleep放到cond.wait(lock)后,它BUG的面目就暴露出来了:

void fun1(){
    while(1)
    {
        count++;
        unique_lock<mutex>lock(_mutex);
        if(count%5 == 0)
        {
            cond.notify_one();
        }
        else
        {
            cout<<"this is fun1,count="<<count<<endl;
        }
        lock.unlock();
    }
}

void fun2()
{
    while(1)
    {
        unique_lock<mutex>lock(_mutex);
        cond.wait(lock);
        sleep(2);
        cout<<"this is fun2,count="<<count<<endl;
        lock.unlock();
    }
}
[mzhai@lock]$ ./a.out
this is fun1,count=1
this is fun1,count=2
this is fun1,count=3
this is fun1,count=4
this is fun2,count=6
this is fun1,count=6
this is fun1,count=7
this is fun1,count=8
this is fun1,count=9
this is fun1,count=11
this is fun1,count=12
this is fun1,count=13
this is fun1,count=14
this is fun1,count=16
this is fun1,count=17
this is fun1,count=18
this is fun1,count=19
this is fun1,count=21

多线程结果不能因随机加了几个sleep就不同,加sleep仅仅是模拟线程调度不大一样了。

再回过头来看看代码哪些地方有问题:

  1. cond.notify_one(); count是5的倍数时,t1会通过notify_one通知t2做事,但并不会阻止t1继续执行。想想一下如果t1执行的很快而t2一直没得到调度,则t1会打印1,2,3,4,6,7,8,9,11...
  2. cond.wait(lock); 可能会假唤醒,此时t1并没有通知它。

那“this is fun2,count=6” 是怎么回事哪?不应该是5吗?一种可能性是(可以通过GDB调试来模拟):

说了那么多,怎么改哪?

 这是一个典型的你等我我等你的例子,对于这个例子都是一方干完事情另一方才能继续,完全串休化的任务,直接写到一个线程里即可。如果说我为了练习线程同步技巧非要整两个线程,那也行,condition_variable官方文档上就有一个例子实现了main线程等待worker_thread完成任务:

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
 
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    // Wait until main() sends data
    std::unique_lock lk(m);
    cv.wait(lk, []{ return ready; });
 
    // after the wait, we own the lock.
    std::cout << "Worker thread is processing data\n";
    data += " after processing";
 
    // Send data back to main()
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";
 
    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lk.unlock();
    cv.notify_one();
}
 
int main()
{
    std::thread worker(worker_thread);
 
    data = "Example data";
    // send data to the worker thread
    {
        std::lock_guard lk(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cv.notify_one();
 
    // wait for the worker
    {
        std::unique_lock lk(m);
        cv.wait(lk, []{ return processed; });
    }
    std::cout << "Back in main(), data = " << data << '\n';
 
    worker.join();
}

我们依样画葫芦:

#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <unistd.h>
using namespace std;
//全局条件变量
condition_variable cond;
mutex _mutex;
bool ready = false;
bool processed = false;

int count = 0;

void fun1(){
    while(1)
    {
        count++;
        unique_lock<mutex> lock1(_mutex);
        if(count%5 == 0)
        {
            ready = true;
            processed = false;
            lock1.unlock();
            cond.notify_one();
            lock1.lock();
            cond.wait(lock1, []{ return processed; });
        }
        else
        {
            cout<<"this is fun1,count="<<count<<endl;
        }
        lock1.unlock();
    }
}

void fun2()
{
    while(1)
    {
        unique_lock<mutex> lock1(_mutex);
        cond.wait(lock1, []{ return ready; });
        cout<<"this is fun2,count="<<count<<endl;
        processed = true;
        ready = false;
        lock1.unlock();
        cond.notify_one();
    }
}

int main()
{
    thread t1(fun1);
    thread t2(fun2);
    t1.join();
    t2.join();
    return 0;
}

结果符合预期,感兴趣的读者可以到处插入sleep测试一下。

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

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

相关文章

无刷电机学习-原理篇

一、无刷电机的优点 使用一项东西首先就要明白为什么要使用它&#xff0c;使用它有什么优点。与有刷电机相比无刷电机除了控制繁琐几乎全是优点。 1、应用范围广&#xff1a;家用电器&#xff08;冰箱空调压缩机、洗衣机、水泵等&#xff09;、汽车、航空航天、消费品工业自动…

STM32之002--软件安装 Keil

文章目录&#xff1a; 一、安装 Keil 二、注册 三、安装芯片支持包 一、安装 Keil 重点 1&#xff1a; 安装时&#xff0c;不能使用中文路径&#xff0c;否则无法正常使用!! 重点 2&#xff1a; 不要安装 V5.36 及以上的版本&#xff0c;其默认AC6编译器&#xff0c…

(二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真

一、创建工作空间 mkdir catkin_ws cd catkin_ws mkdir src cd src 二、下载wpr_simulation源码 git clone https://github.com/6-robot/wpr_simulation.git 三、编译 ~/catkin_make 目录下catkin_makesource devel/setup.bash 四、运行 roslaunch wpr_simulation wpb_s…

priority_queue的使用与模拟实现(容器适配器+stack与queue的模拟实现源码)

priority_queue的使用与模拟实现 引言&#xff08;容器适配器&#xff09;priority_queue的介绍与使用priority_queue介绍接口使用默认成员函数 size与emptytoppush与pop priority_queue的模拟实现构造函数size与emptytoppush与pop向上调整建堆与向下调整建堆向上调整建堆向下调…

ssh: connect to host github.com port 22: Connection refused

ssh: connect to host github.com port 22: Connection refused 问题现象 本文以Windows系统为例进行说明&#xff0c;在个人电脑上使用Git命令来操作GitHub上的项目&#xff0c;本来都很正常&#xff0c;突然某一天开始&#xff0c;会提示如下错误ssh: connect to host gith…

通讯录项目的实现以及动态顺序表(基于顺序表)

首先我们要知道什么是顺序表: 顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝,顺序表分为静态顺序表(使⽤定⻓数组存储元素)和动态顺序表(按需申请) 静态顺序表缺点: 空间给少了不够⽤,给多了造成空间浪费 拿出来我之前以及写好了的顺序表的代码:…

LeetCode、162. 寻找峰值【中等,最大值、二分】

文章目录 前言LeetCode、162. 寻找峰值【中等&#xff0c;最大值、二分】题目及类型思路及代码思路1&#xff1a;二分思路2&#xff1a;寻找最大值 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿…

微信小程序(七)navigator点击效果

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.默认效果 2.无效果 3.激活效果 源码&#xff1a; index.wxml //如果 <navigator url"/pages/logs/logs">跳转到log页面&#xff08;默认&#xff09; </navigator><navigator url&q…

ctfshow命令执行(web29-web52)

目录 web29 web30 web31 web32 web33 web34 web35 web36 web37 web38 web39 web40 web41 web42 web43 web44 web45 web46 web47 web48 web49 web50 web51 web52 web29 <?php error_reporting(0); if(isset($_GET[c])){$c $_GET[c];if(!preg_match…

Rust基础语法1

所有权转移&#xff0c;Rust中没有垃圾收集器&#xff0c;使用所有权规则确保内存安全&#xff0c;所有权规则如下&#xff1a; 1、每个值在Rust中都有一个被称为其所有者&#xff08;owner&#xff09;的变量&#xff0c;值在任何时候只能有一个所有者。 2、当所有者离开作用域…

MacBookPro怎么数据恢复? mac电脑数据恢复?

使用电脑的用户都知道&#xff0c;被删除的文件一般都会经过回收站&#xff0c;想要恢复它直接点击“还原”就可以恢复到原始位置。mac电脑同理也是这样&#xff0c;但是“回收站”在mac电脑显示为“废纸篓”。 如果电脑回收站&#xff0c;或者是废纸篓里面的数据被清空了&…

【GitHub项目推荐--微软开源的可视化工具】【转载】

说到数据可视化&#xff0c;大家都很熟悉了&#xff0c;设计师、数据分析师、数据科学家等&#xff0c;都需要用各种方式各种途径做着数据可视化的工作.....当然许多程序员在工作中有时也需要用到一些数据可视化工具&#xff0c;如果工具用得好&#xff0c;就可以把原本枯燥凌乱…

springcloud Client端cloud-consumer-order80

文章目录 简介建立module修改pom修改yml主启动类把公共代码写在一个mudule 里面测试 简介 这个是和之前的8001相互配合端口测试 这里的80的用户测试端口。 代码在&#xff1a;GitHub 上&#xff1a;https://github.com/13thm/study_springcloud/tree/main/days2 建立module …

C++无锁队列的原理与实现

目录 1.无锁队列原理 1.1.队列操作模型 1.2.无锁队列简介 1.3.CAS操作 2.无锁队列方案 2.1.boost方案 2.2.ConcurrentQueue 2.3.Disruptor 3.无锁队列实现 3.1.环形缓冲区 3.2.单生产者单消费者 3.3.多生产者单消费者 3.4.RingBuffer实现 3.5.LockFreeQueue实现 …

django admin后台中进行多个手机号解密消耗时间对比

需求&#xff1a; 1 手机号在数据库中是使用rsa方式加密存储&#xff0c;后台查看中需要转换为明文&#xff0c;因为需要解密多个手机号&#xff0c;所以在后台查看中消耗时间3秒&#xff0c;希望通过多线程&#xff0c;多进程&#xff0c;异步方式来缩短时间 相关注意点&…

如何实现查找附近的人-GEO

背景 打开美团&#xff0c;可以通过自身定位查看附近的商品。打开社交软件&#xff0c;可以查看附近的人交友。打开滴滴&#xff0c;可以查看的附近的共享单车&#xff0c;那这些是如何实现&#xff1f; Redis GEO Redis GEO 主要用于存储地理位置信息&#xff0c;并对存储的…

Ubuntu系统Git的安装配置及使用笔记(更新中)

Ubuntu下Git的下载及配置 (1)、下载git 打开终端命令窗口,输入&#xff1a;sudo apt-get install git 提示&#xff1a;sudo命令是用来以其他身份来执行命令&#xff0c;预设的身份为root,使用sudo时必须先输入密码 (2)、可以使用命令git --version查看git的版本号 (3)、设置…

排序链表(LeetCode 148)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路参考文献 1.问题描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff…

git中合并分支时出现了代码冲突怎么办

目录 第一章、Git代码冲突介绍1.1&#xff09;什么是Git代码冲突①git merge命令介绍②代码冲突原因 1.2&#xff09;提示代码冲突的两种情况①本地不同分支的文件有差异时&#xff1a;②本地仓库和git远程仓库的文件有差异时&#xff1a; 1.3&#xff09;解决合并时的代码冲突…

【冥想X理工科思维】场景7:背锅挨批后…

冥想音频合集&#xff1a;职场解压冥想音频 压力场景&#xff1a; 明明不是我的错&#xff0c;却不得不替领导背锅&#xff0c;受到大老板和其它团队领导的疯狂指责&#xff0c;如何借助冥想&#xff0c;处理批评后的负面情绪&#xff1f; 点击看大图&#xff1a; 详细说明&…