多线程c++

目录

1.join和detach区别

2.lock_guard和unique_lock 

3.原子操作

4.条件变量condition_variable 

5.future 和 promise 


1.join和detach区别

①不使用join和detach

#include <iostream>
#include <thread>
#include <windows.h>using namespace std;void t1()  //普通的函数,用来执行线程
{for (int i = 0; i < 10; ++i){cout <<" t1 :"<< i <<"  ";_sleep(1);}
}
void t2()
{for (int i = 11; i < 20; ++i){cout <<" t2 :"<< i <<"  ";_sleep(1);}
}
int main()
{thread th1(t1);  //实例化一个线程对象th1,使用函数t1构造,然后该线程就开始执行了(t1())thread th2(t2);cout << "here is main\n\n";system("pause");return 0;
}

每次输出的结果都不一样

②使用join

#include <iostream>
#include <thread>
#include <windows.h>using namespace std;void t1()  //普通的函数,用来执行线程
{for (int i = 0; i < 10; ++i){cout <<" t1 :"<< i <<"  ";_sleep(1);}
}
void t2()
{for (int i = 11; i < 20; ++i){cout <<" t2 :"<< i <<"  ";_sleep(1);}
}
int main()
{thread th1(t1);  //实例化一个线程对象th1,使用函数t1构造,然后该线程就开始执行了(t1())thread th2(t2);th1.join(); // 必须将线程join或者detach 等待子线程结束主进程才可以退出th2.join(); cout << "here is main\n\n";system("pause");return 0;
}

join会将主线程和子线程th1 th2分离,子线程执行完才会执行主线程。(两个子线程输出每次仍不一样)

③使用detach

#include <iostream>
#include <thread>#include <windows.h>using namespace std;void t1()  //普通的函数,用来执行线程
{for (int i = 0; i < 10; ++i){cout <<" t1 :"<< i <<"  ";_sleep(1);}}
void t2()
{for (int i = 11; i < 20; ++i){cout <<" t2 :"<< i <<"  ";_sleep(1);}
}
int main()
{thread th1(t1);  //实例化一个线程对象th1,使用函数t1构造,然后该线程就开始执行了(t1())thread th2(t2);th1.detach();th2.detach();cout << "here is main\n\n";system("pause");return 0;
}

detach也会将主线程和子线程th1 th2分离,但是主线程执行过程子线程仍会执行

总结:

1. join会阻塞当前的线程,直到运行的线程结束。(例如上面第二段代码主线程被阻塞,直到子线程执行完才会执行join之后的主线程代码)

2.detach从线程对象中分离出执行线程,允许线程独立的执行。(上面第三段代码,主线程和两个子线程独立的执行)

参考文章

2.lock_guard和unique_lock 

线程的使用一定需要搭配锁mutex,它是用来保证线程同步的,防止不同的线程同时操作同一个共享数据。

①使用mutex,使用lock函数上锁,unlock解锁

#include <iostream>
#include <thread>
#include <mutex>
#include <stdlib.h>int cnt = 20;
std::mutex m;
void t1()
{while (cnt > 0){    m.lock();if (cnt > 0){--cnt;std::cout << cnt << std::endl;}m.unlock();}
}
void t2()
{while (cnt < 20){m.lock();if (cnt < 20){++cnt;std::cout << cnt << std::endl;}m.unlock();}
}int main(void)
{std::thread th1(t1);std::thread th2(t2);th1.join();    //等待t1退出th2.join();    //等待t2退出std::cout << "here is the main()" << std::endl;system("pause");return 0;
}

②使用lock_guard

使用mutex比较繁琐,需要上锁和解锁,c++提供了lock_guard,它是基于作用域的,能够自解锁,当该对象创建时,它会像m.lock()一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程。

#include <iostream>
#include <thread>
#include <mutex>
#include <stdlib.h>int cnt = 20;
std::mutex m;
void t1()
{while (cnt > 0){    std::lock_guard<std::mutex> lockGuard(m);// m.lock();if (cnt > 0){--cnt;std::cout << cnt << std::endl;}// m.unlock();}
}
void t2()
{while (cnt < 20){std::lock_guard<std::mutex> lockGuard(m);// m.lock();if (cnt < 20){++cnt;std::cout << cnt << std::endl;}// m.unlock();}
}int main(void)
{std::thread th1(t1);std::thread th2(t2);th1.join();    //等待t1退出th2.join();    //等待t2退出std::cout << "here is the main()" << std::endl;system("pause");return 0;
}

③使用unique_lock

lock_guard有个很大的缺陷,在定义lock_guard的地方会调用构造函数加锁,在离开定义域的话lock_guard就会被销毁,调用析构函数解锁。这就产生了一个问题,如果这个定义域范围很大的话,那么锁的粒度就很大,很大程序上会影响效率。

因此提出了unique_lock,这个会在构造函数加锁,然后可以利用unique.unlock()来解锁,所以当锁的颗粒度太多的时候,可以利用这个来解锁,而析构的时候会判断当前锁的状态来决定是否解锁,如果当前状态已经是解锁状态了,那么就不会再次解锁,而如果当前状态是加锁状态,就会自动调用unique.unlock()来解锁。而lock_guard在析构的时候一定会解锁,也没有中途解锁的功能。

参考文章

3.原子操作

原子性操作库(atomic)是C++11中新增的标准库,它提供了一种线程安全的方式来访问和修改共享变量,避免了数据竞争的问题。在多线程程序中,如果多个线程同时对同一个变量进行读写操作,就可能会导致数据不一致的问题。原子性操作库通过使用原子操作来保证多个线程同时访问同一个变量时的正确性。

例,在多线程中进行加一减一操作,循环一定次数

#include <iostream>
#include <thread>
#include <atomic>
#include <time.h>
#include <mutex>
using namespace std;#define MAX 100000
#define THREAD_COUNT 20int total = 0;
mutex mt;void thread_task()
{for (int i = 0; i < MAX; i++){mt.lock();total += 1;total -= 1;mt.unlock();}
}int main()
{clock_t start = clock();thread t[THREAD_COUNT];for (int i = 0; i < THREAD_COUNT; ++i){t[i] = thread(thread_task);}for (int i = 0; i < THREAD_COUNT; ++i){t[i].join();}clock_t finish = clock();// 输出结果cout << "result:" << total << endl;cout << "duration:" << finish - start << "ms" << endl;system("pause");return 0;
}

从结果来看非常耗时,使用原子atomic,不需要使用mutex,(注意添加头文件)

#include <iostream>
#include <thread>
#include <atomic>
#include <time.h>
#include <mutex>
using namespace std;#define MAX 100000
#define THREAD_COUNT 20//原子操作  也不需要使用互斥锁
// atomic_int total(0);
atomic<int> total;void thread_task()
{for (int i = 0; i < MAX; i++){total += 1;total -= 1;}
}int main()
{clock_t start = clock();thread t[THREAD_COUNT];for (int i = 0; i < THREAD_COUNT; ++i){t[i] = thread(thread_task);}for (int i = 0; i < THREAD_COUNT; ++i){t[i].join();}clock_t finish = clock();// 输出结果cout << "result:" << total << endl;cout << "duration:" << finish - start << "ms" << endl;system("pause");return 0;
}

 关于具体函数的使用参考官方文档

4.条件变量condition_variable 

条件变量是c++11引入的一种同步机制,它可以阻塞一个线程或者多个线程,直到有线程通知或者超时才会唤醒正在阻塞的线程, 条件变量需要和锁配合使用,这里的锁就是上面的unique_lock。

其中有两个非常重要的接口,wait()和notify_one(),wait()可以让线程陷入休眠状态,notify_one()就是唤醒真正休眠状态的线程。还有notify_all()这个接口,就是唤醒所有正在等待的线程。

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;deque<int> q;
mutex mt;
condition_variable cond;void thread_producer()
{int count = 10;while (count > 0){unique_lock<mutex> unique(mt);q.push_front(count);unique.unlock();    // 解锁才能去唤醒cout << "producer a value: " << count << endl;cond.notify_one();   // 唤醒wait的线程会阻塞当前线程,this_thread::sleep_for(chrono::seconds(1));count--;}
}void thread_consumer()
{int data = 0;while (data != 1){unique_lock<mutex> unique(mt);while (q.empty())cond.wait(unique);      //解锁,线程被阻塞处于等待状态 |||| 被唤醒后优先获得互斥锁data = q.back();   // 使用 back 函数获取最后一个元素q.pop_back();cout << "consumer a value: " << data << endl;// 下面这行可以不写,unique_lock 离开作用域会调用析构判断是否解锁。// unique.unlock();     // 解锁后thread_producer获得互斥锁}
}int main()
{thread t1(thread_consumer);thread t2(thread_producer);t1.join();t2.join();system("pause");return 0;
}

thread_consumer 的执行流程如下

1.unique_lock<mutex> unique(mt); - 上锁互斥锁。

2.cond.wait(unique); - 释放互斥锁并等待通知。

3.当被通知后,重新获得互斥锁。

4.继续执行后面的代码,包括 data = q.back(); 和 q.pop_back();。

在上述过程中,unique_lock 会自动处理锁的上锁和解锁操作。当 cond.wait(unique); 执行时,它会将互斥锁解锁,并将线程置于等待状态。当线程被唤醒后,unique_lock 会自动重新对互斥锁上锁。

在 thread_producer 中,它获取互斥锁的步骤如下

1.unique_lock<mutex> unique(mt); - 上锁互斥锁。

2.q.push_front(count); - 操作共享资源。

3.unique.unlock(); - 解锁互斥锁。

4.cond.notify_one(); - 发送通知。

在这个过程中,unique_lock 会在 unique.unlock(); 处解锁互斥锁,以允许其他线程进入相应的临界区。当 cond.notify_one(); 发送通知后,thread_consumer 会被唤醒,并在 unique_lock 重新获取互斥锁后继续执行。

这两个互斥锁操作是同步的,不会引起冲突,因为它们是针对不同的互斥锁对象进行的。thread_consumer 上的互斥锁 mt 与 thread_producer 上的互斥锁 mt 是两个不同的互斥锁对象。

注意:在使用条件变量时,一般要在循环中等待条件,因为线程被唤醒后需要重新检查条件是否真的满足。 

更详细的参考文档

5.future 和 promise 

TODO

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

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

相关文章

hcip---ospf综合实验

一&#xff1a;实验要求 1、R4为ISP&#xff0c;其上只能配置IP地址&#xff0c;R4与其所有直连设备间均使用公有IP 2、R3-R5/6/7为MGRE环境&#xff0c;R3为中心站点 3、整个OSPF环境IP基于R4的环回 4、所有设备均可访问R4的环回 5、减少LSA的更新量&#xff0c;加快收敛…

医院如何筛选安全合规的内外网文件交换系统?

医院内外网文件交换系统是专为医疗机构设计的&#xff0c;用于在内部网络&#xff08;内网&#xff09;和外部网络&#xff08;外网&#xff09;之间安全、高效地传输敏感医疗数据和文件的解决方案。这种系统对于保护患者隐私、遵守医疗数据保护法规以及确保医疗服务的连续性和…

初探分布式链路追踪

本篇文章&#xff0c;主要介绍应用如何正确使用日志系统&#xff0c;帮助用户从依赖、输出、清理、问题排查、报警等各方面全面掌握。 可观测性 可观察性不单是一套理论框架&#xff0c;而且并不强制具体的技术规格。其核心在于鼓励团队内化可观察性的理念&#xff0c;并确保由…

Django4.2(DRF)+Vue3 读写分离项目部署上线

文章目录 1 前端2 后端2.1 修改 settings.py 文件关于静态文件2.2 关于用户上传的文件图片 3 Nginx4 镜像制作4.1 nginx4.3 Django镜像4.3.1 构建 5 docker-compose 文件内容 1 前端 进入前端项目的根目录&#xff0c;运行如下命令进行构建 npm run build构建完成后&#xff…

K8S之Pod的介绍和使用

Pod的理论和实操 pod理论说明Pod介绍Pod运行与管理Pod管理多个容器Pod网络Pod存储 Pod工作方式自主式Pod控制器管理的Pod&#xff08;常用&#xff09; 创建pod的流程 pod实操通过资源清单文件创建自主式pod通过kubectl run创建Pod&#xff08;不常用&#xff09; pod理论说明 …

指针的深入了解6

1.回调函数 回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另一个函数&#xff0c;当这个指针被用来调用其所指向的函数 时&#xff0c;被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用&#xff0…

【LVGL源码移植环境搭建】

LVGL源码移植&环境搭建 ■ LVGL源码移植■ 下载LVGL源码■ 修改LVGL文件夹■■■■ 视频链接 Ubuntu模拟器环境建置 ■ LVGL源码移植 ■ 下载LVGL源码 LVGL源码 我们以选择v8.2.0为例&#xff0c;选择8.2.0下载 ■ 修改LVGL文件夹 1.我们只需要关注这5个文件即可&…

《Docker技术革命:从虚拟机到容器化,全面解析Docker的原理与应用-上篇》

文章目录 Docker为什么会出现总结 Docker的思想Docker历史总结 Docker能干嘛虚拟机技术虚拟机技术的缺点 容器化技术Docker和虚拟机技术的区别 Docker概念Docker的基本组成镜像&#xff08;image)容器&#xff08;container&#xff09;仓科&#xff08;repository&#xff09;…

GitHub工作流的使用笔记

文章目录 前言1. 怎么用2. 怎么写前端案例1&#xff1a;自动打包到新分支前端案例2&#xff1a;自动打包推送到gitee的build分支案例3&#xff1a;暂时略 前言 有些东西真的就是要不断的试错不断地试错才能摸索到一点点&#xff0c;就是摸索到凌晨两三点第二天要8点起床感觉要…

聊一聊GPT、文心、通义、混元

我使用同一个Prompt提示词“请以记叙文的文体来写”&#xff0c;分别发送给GPT-3.5&#xff08;调用API&#xff09;、文心、通义、混元&#xff0c;下面是它们各自生成的文本内容&#xff0c;大家一看便知了。 GPT-3.5&#xff1a; 在我个人使用GPT模型的过程中&#xff0c;我…

Facebook的创新征程:社交媒体的演进之路

在当今数字化时代&#xff0c;社交媒体已经成为人们生活中不可或缺的一部分&#xff0c;而Facebook作为社交媒体领域的巨头&#xff0c;一直在不断创新和演进。本文将深入探讨Facebook的创新征程&#xff0c;追溯其社交媒体的发展历程&#xff0c;探讨其对用户、社会和数字时代…

echart 实现自定义地图

先上效果图 需求&#xff1a;自定义区域平面图&#xff0c;支持区域高亮 // 2D详情const initChartsMapItemB async (flow: any, mapbg: any) > {// mapbg 为svg的地址 import mapbg from //assets/json/map/F42d.svgconst svgData (await request.get(mapbg)) as anye…

WPF应用程序(.Net Framework 4.8) 国际化

1、新建两个资源字典文件zh-CN.xaml和en-US.xaml&#xff0c;分别存储中文模板和英文模板 (1) zh-CN.xaml <ResourceDictionary xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml&q…

基于链表实现贪吃蛇游戏

本文中&#xff0c;我们将使用链表和一些Win32 API的知识来实现贪吃蛇小游戏 一、功能 &#xff08;1&#xff09;游戏载入界面 &#xff08;2&#xff09;地图的绘制 &#xff08;3&#xff09;蛇身的移动和变长 &#xff08;4&#xff09;食物的生成 &#xff08;5&…

CentOS 7 部署 ZeroTier Moon 节点

ZeroTier是一套使用UDP协议构建的SD-WAN网络软件&#xff0c;其主要有三部分组成&#xff1a;行星服务器Planet、月亮服务器Moon、客户端节点LEFA&#xff0c;行星服务器是ZeroTier的根节点&#xff0c;可以采用ZeroTier官方的服务器&#xff0c;也可以使用开源代码自行搭建 月…

JAVA处理类似饼状图占比和100%问题,采用最大余额法

前言&#xff1a; 在做数据统计报表的时候&#xff0c;有两种方式解决占比总和达不到100%或者超过100%问题。 第一种方式是前端echart图自带的算分框架。 第二种方式是java后端取处理这个问题。 现存问题&#xff1a; 前端通过饼状图的方式去展示各个分类的占比累加和为100%问题…

公司宣传电子画册的制作方法

​制作公司宣传电子画册是一种非常有效的方式&#xff0c;可以展示公司的形象和产品&#xff0c;同时也可以吸引更多的潜在客户。不仅低碳环保&#xff0c;还省了不少人力和财力&#xff0c;只要一个二维码、一个链接就能随时随地访问公司的宣传画册。以下是一些制作电子画册的…

TSINGSEE青犀视频智慧电梯管理平台,执行精准管理、提升乘梯安全

一、方案背景 随着城市化进程的不断加快&#xff0c;我国已经成为全球最大的电梯生产和消费市场&#xff0c;电梯也成为人们日常生活中不可或缺的一部分。随着电梯数量的激增&#xff0c;电梯老龄化&#xff0c;维保数据不透明&#xff0c;物业管理成本高&#xff0c;政府监管…

openGauss学习笔记-211 openGauss 数据库运维-高危操作一览表

文章目录 openGauss学习笔记-211 openGauss 数据库运维-高危操作一览表211.1 禁止操作211.2 高危操作 openGauss学习笔记-211 openGauss 数据库运维-高危操作一览表 各项操作请严格遵守指导书操作&#xff0c;同时避免执行如下高危操作。 211.1 禁止操作 表1中描述在产品的操…

【golang】22、functional options | 函数式编程、闭包

文章目录 一、配置 Option1.1 options1.2 funcitonal options 一、配置 Option 1.1 options https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html I’ve been trying on and off to find a nice way to deal with setting options in a…