并发 多线程

devtools/2024/9/24 3:19:58/

目录

thread

thread 类总览

构造函数

= join joinable

​编辑

detach swap yield swap

成员函数的调用

namespace::this_thread

线程同步--锁

互斥锁mutex

递归锁recursive_mutex

定时锁

Lock 锁辅助类

lock_guard​编辑

unique_lock

std::lock 解决死锁问题

消息队列

condition_variable

condition_variable

生产者消费者模型

atomic

call_once

​chrono 时间库


thread

thread 类总览

构造函数

= join joinable

  • 谁调用了这个函数?调用了这个函数的线程对象,一定要等这个线程对象的方法(在构造时传入的方法)执行完毕后(或者理解为这个线程的活干完了!),这个join()函数才能得到返回。

  • 在什么线程环境下调用了这个函数?上面说了必须要等线程方法执行完毕后才能返回,那必然是阻塞调用线程的,也就是说如果一个线程对象在一个线程环境调用了这个函数,那么这个线程环境就会被阻塞,直到这个线程对象在构造时传入的方法执行完毕后,才能继续往下走,另外如果线程对象在调用join()函数之前,就已经做完了自己的事情(在构造时传入的方法执行完毕),那么这个函数不会阻塞线程环境,线程环境正常执行

detach swap yield swap

成员函数的调用

必须传入成员函数地址和调用的对象

namespace::this_thread

线程同步--锁

互斥锁mutex

递归锁recursive_mutex

递归锁出现的意义:假设存在这样一个场景,一个函数使用mutex 同时调用另外的一个函数里面有用到同一个mutex,则此时同一个互斥量被上了两次锁,导致死锁;而递归锁可以对互斥量拥有多层所有权,可以避免死锁;

同一个线程多次占用(递归占用次数有限,不能太多),可配合lock_guard使用,不通线程和互斥锁一致。

定时锁

Lock 锁辅助类

lock_guard

unique_lock

比lock_guard更灵活,主要用与条件变量一同使用

std::lock 解决死锁问题

 

消息队列

#include <list>
#include <thread>
#include <mutex>
#include<iostream>
​
class A
{
public://把收到的消息(玩家命令)入到一个队列的线程void inMsgRecvQueue(){for (int i = 0;i < 100;++i){cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;my_mutex.lock();msgRecvQueue.push_back(i);my_mutex.unlock();}}
​bool outMsgLULProc(int& command){// my_mutex.lock();std::lock_guard<std::mutex> sguard(my_mutex);if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();// my_mutex.unlock();return true;}// my_mutex.unlock();return false; }
​//把数据从消息队列中取出的线程void outMsgRecvQueue(){int command = 0;for (int i = 0;i < 100;i++){bool result = outMsgLULProc(command);if (result == true){cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;//可以考虑对命令(数据)进行处理}else{cout << "outMsgRecvQueue()执行,但是目前消息队列中为空" << i << endl;}}cout << "end" << endl;}
​
private:std::list<int> msgRecvQueue;    //容器,专门用于代表玩家发送过来的命令std::mutex my_mutex; //创建一个互斥量 
};
​
int main()
{A myobja;std::thread myOutnMsgObj(&A::outMsgRecvQueue,&myobja);std::thread myInMsgObj(&A::inMsgRecvQueue,&myobja);myInMsgObj.join();myOutnMsgObj.join();return 0;
}

condition_variable

condition_variable

生产者消费者模型

//condition_variable.h头文件#include <mutex>
#include <thread>
#include <chrono>
#include <deque>
#include <condition_variable>
​
namespace TestConditional_variable
{extern std::mutex g_cvMutex;extern std::condition_variable g_cv;extern std::deque<int>g_data_deque;extern const int MAX_NUM;extern int g_next_index;
​const int PRODUCER_THREAD_NUM = 3;const int CONSUMER_THREAD_NUM = 3;
​void producer_thread(int thread_id);void consumer_thread(int thread_id);
​
}//condition_variable.cpp头文件#include"Condition_variable.h"
#include<iostream>
namespace TestConditional_variable {
​std::mutex g_cvMutex;std::condition_variable g_cv;std::deque<int>g_data_deque;const int MAX_NUM = 30;
​int g_next_index = 0;void producer_thread(int thread_id){for (int i = 0; i < 4;i++){std::this_thread::sleep_for(std::chrono::milliseconds(500));std::unique_lock<std::mutex>lk(g_cvMutex);g_cv.wait(lk, [](){return g_data_deque.size() <= MAX_NUM; });//wait的第二个参数为可执行的OBJ 反复执行直到返回trueg_next_index++;g_data_deque.push_back(g_next_index);std::cout << "producer_thread" << thread_id << " producer data" << g_next_index;std::cout << " queue size:" << g_data_deque.size() << std::endl;g_cv.notify_all();}}
​void consumer_thread(int thread_id){for (int i = 0; i < 4; i++){std::this_thread::sleep_for(std::chrono::milliseconds(500));std::unique_lock<std::mutex>lk(g_cvMutex);g_cv.wait(lk, [](){return !g_data_deque.empty(); });g_next_index++;int data = g_data_deque.front();g_data_deque.pop_front();std::cout << "consumer_thread" << thread_id << " consumer data" << g_next_index;std::cout << " queue size:" << g_data_deque.size() << std::endl;g_cv.notify_all();}}
​
}#include"Condition_variable.h"
#include<iostream>
​
int main(int argc, char *argv[])
{std::thread arrProducerThread[TestConditional_variable::PRODUCER_THREAD_NUM];std::thread arrConsumerThread[TestConditional_variable::CONSUMER_THREAD_NUM];
​for (int i = 0; i < TestConditional_variable::PRODUCER_THREAD_NUM; i++){arrProducerThread[i] = std::thread(TestConditional_variable::producer_thread, i);}for (int i = 0; i < TestConditional_variable::CONSUMER_THREAD_NUM; i++){arrConsumerThread[i] = std::thread(TestConditional_variable::consumer_thread, i);}for (int i = 0; i < TestConditional_variable::PRODUCER_THREAD_NUM; i++){arrProducerThread[i].join();}for (int i = 0; i < TestConditional_variable::CONSUMER_THREAD_NUM; i++){arrConsumerThread[i].join();}return 0;
}

atomic

原子变量

call_once

        在某些特定情况下,某些函数只能在多线程环境下调用一次,比如:要初始化某个对象而这个对象只能被初始化一次,就可以使用 std::call_once() 来保证函数在多线程环境下只能被调用一次。使用 call_once() 的时候,需要一个 once_flag 作为 call_once() 的传入参数。

           void call_once( std::once_flag& flag, Callable&& f, Args&&... args );

          flag:once_flag 类型的对象,要保证这个对象能够被多个线程同时访问到。

          f:回调函数,可以传递一个有名函数地址,也可以指定一个匿名函数

         args:作为实参传递给回调函数。

   

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;once_flag g_flag;
void do_once(int a, string b)
{cout << "name: " << b << ", age: " << a << endl;
}void do_something(int age, string name)
{static int num = 1;call_once(g_flag, do_once, 19, "luffy");cout << "do_something() function num = " << num++ << endl;
}void test_call_once()
{thread t1(do_something, 20, "ace");thread t2(do_something, 20, "sabo");thread t3(do_something, 19, "luffy");t1.join();t2.join();t3.join();
}

chrono 时间库

duration

定义于头文件 <chrono>
template<
    class Rep,
    class Period = std::ratio<1>
> class duration;

这是一个数值类型,表示时钟数(周期)的类型(默认为整形)。若 Rep 是浮点数,则 duration 能使用小数描述时钟周期的数目
Period:表示时钟的周期,它的原型如下
// 定义于头文件 <ratio>
template<
    std::intmax_t Num,//周期的分子
    std::intmax_t Denom = 1//周期的分母 默认为1
> class ratio;

ratio 类表示每个时钟周期的秒数,其中第一个模板参数 Num代表分子,Denom代表分母,该分母值默认为 1,因此,ratio代表的是一个分子除以分母的数值,比如:ratio<2> 代表一个时钟周期是 2 秒,ratio<60 > 代表一分钟,ratio<60*60 > 代表一个小时,ratio<60*60*24 > 代表一天。而 ratio<1,1000 > 代表的是 1/1000 秒,也就是 1 毫秒,ratio<1,1000000 > 代表一微秒,ratio<1,1000000000 > 代表一纳秒。

void test_chrono()
{using namespace std;chrono::hours h(1);                          // 一小时chrono::milliseconds ms{ 3 };                // 3 毫秒  初始化操作 ms{3} 表示一共有 3 个时间周期,每个周期为 1 毫秒std::chrono::microseconds us = 2 * ms;     // 6000 微秒chrono::duration<int, ratio<1000>> ks(3);    // 3000 秒// chrono::duration<int, ratio<1000>> d3(3.5);  // error//dd(6.6) 时钟周期为默认的 1 秒,共有 6.6 个时钟周期,所以 dd 表示的时间间隔为 6.6 秒chrono::duration<double> dd(6.6);               // 6.6 秒 周期类型为小数// 使用小数表示时钟周期的次数//hz(3.5) 时钟周期为 1 / 30 秒,共有 3.5 个时钟周期,所以 hz 表示的时间间隔为 1 / 30 * 3.5 秒chrono::duration<double, std::ratio<1, 30>> hz(3.5);//count统计周期数std::cout << "3 ms duration has " << ms.count() << " ticks\n"  //3<< "6000 us duration has " << us.count() << " ticks\n"     //6000<< "3.5 hz duration has " << hz.count() << " ticks\n";     //3.5//重载了++ -- + - = 等操作chrono::minutes t1(2);chrono::seconds t2(2);chrono::seconds t3 = t1 - t2;chrono::seconds t4 = t1 + t2;t4++;cout << t3.count() <<"seconds" << endl;//118secondscout << t4.count() << "seconds" << endl;//123seconds/*注意事项:duration 的加减运算有一定的规则,当两个 duration 时钟周期不相同的时候,会先统一成一种时钟,然后再进行算术运算,统一的规则如下:假设有 ratio<x1,y1> 和 ratio<x2,y2 > 两个时钟周期,首先需要求出 x1,x2 的最大公约数 X,然后求出 y1,y2 的最小公倍数 Y,统一之后的时钟周期 ratio 为 ratio<X,Y>*/chrono::duration<double, ratio<9, 7> >t5(3);//3*   9/7秒chrono::duration<double, ratio<4, 3>> t6(3); // 3 *   4/3秒chrono::duration<double, ratio<1, 21>> t7 = t6 - t5;cout << t7.count() << "ticks" << endl;//3ticks}

http://www.ppmy.cn/devtools/56039.html

相关文章

Vue 2.0 与 3.0区别

Vue.js是一种流行的前端JavaScript框架&#xff0c;用于构建用户界面和单页面应用程序。随着时间的推移&#xff0c;Vue.js已经从Vue2发展到了Vue3&#xff0c;这两个版本在**生命周期、模板组件以及性能**等方面有显著差异。具体分析如下&#xff1a; 1. **生命周期** - **Vue…

C++中int、DWORD和QWORD

当谈论C编程语言时&#xff0c;以下术语经常被提及&#xff1a;int、DWORD和QWORD。它们是用于表示不同数据类型和长度的关键字。以下是它们的详细解释以及举例说明&#xff1a; int&#xff1a; int是C中表示整数的数据类型之一。它通常用于存储有符号的整数值。int的长度在不…

学习python笔记:10,requests,enumerate,numpy.array

requests库&#xff0c;用于发送 HTTP 请求的 Python 库。 requests 是一个用于发送 HTTP 请求的 Python 库。它使得发送 HTTP 请求变得简单且人性化。以下是一些基本的 requests 函数及其用途&#xff1a; requests.get(url, **kwargs) 发送一个 GET 请求到指定的 URL。 i…

如何恢复电脑硬盘删除数据?提供一套实用恢复方案

在数字化时代&#xff0c;电脑硬盘中存储的数据对于个人和企业来说都至关重要。然而&#xff0c;有时我们可能会不小心删除了一些重要文件&#xff0c;或者因为某种原因导致数据丢失。这时候&#xff0c;恢复硬盘上被删除的数据就显得尤为重要。本文将为您提供一套实用的电脑硬…

代码随想录算法跟练 | Day14 | 二叉树 Part01

个人博客主页&#xff1a;http://myblog.nxx.nx.cn 代码GitHub地址&#xff1a;https://github.com/nx-xn2002/Data_Structure.git Day14 今天&#xff0c;主要是二叉树的基础知识&#xff0c;包括二叉树的结构、存储方式和遍历方式 二叉树的结构 二叉树顾名思义&#xff0…

区块链技术的核心要素:共识机制、加密技术与分布式账本

区块链听起来像个非常高大上的技术&#xff0c;其实它的核心原理并不难理解。今天我们要聊的就是区块链的三个核心要素&#xff1a;共识机制、加密技术和分布式账本。想象一下区块链是一个巨大的数字笔记本&#xff0c;我们要弄清楚大家如何共同写这个笔记本&#xff0c;又如何…

CSS|05 继承性与优先级

继承性 一、继承性的特点&#xff1a; 1.外层元素身上的样式会被内层元素所继承 2.如果内层元素与外层元素身上的演示相同时&#xff0c;外层元素的样式会被内层元素所覆盖 二、关于继承性的问题 是不是所有样式都能被继承&#xff1f; 答&#xff1a;并不是所有样式能被继承…

计网实训——不相同网段的PC相互通信

目录 提前准备APP路由器指令 实验一1、实验需求&#xff08;1&#xff09;实现同网段的PC相互通信。&#xff08;2&#xff09;实现不相同网段的PC相互通信。&#xff08;3&#xff09;分析相同和不同网段PC通信时MAC地址的变化。 2、实验拓扑3、实验步骤及实验截图&#xff08…