一步一步写线程之二线程应用管理

一、多线程使用

多线程在实际的应用中非常广泛,它在实际应用中遇到的主要的问题有以下几类:
1、线程自身的控制
线程自身的控制包括:线程结束控制(join/detach),所有权控制和数量选择。
2、线程的传参
线程的传参一般是指线程初始启动时得到数据参数,这个某些情况下很容易被忽略。
3、线程的同步
这是线程的复杂之处,同步一般指多线程间要交互数据,即使某些任务同步,其实最终也要落实到数据的同步。
为了统一、方便分析使用,本系列一般使用标准库的线程。

二、线程的管理

既然知道了线程的应用的场景就可以从这其中进行分析:
1、线程的ID获得
主线程可以使用: auto id = std::this_thread::get_id();
子线程可以使用:auto id = th.get_id();//th是创建线程时的句柄
2、当前CPU支持的核心数量获取
获得当前最佳线程数量:auto count = std::thread::hardware_concurrency();
这个需要说明的是,在实际应用中要区分是CPU密集性应用还是IO密集性应用等情况,这个数量才有参考意义。
3、线程的分离
这个非常简单:

int data = 0;
std::thread th = std::thread([&](int d) {std::cout << "this data is:" << d << std::endl;},std::ref(data));
auto id = th.get_id();
th.detach();

需要说明的是,线程的分离的使用,意味着线程需要自己控制状态,否则会出现各种意想不到的结果。

4、线程的加入等待
看使用即可明白:

int data = 0;
std::thread th = std::thread([&](int d) {std::cout << "this data is:" << d << std::endl;},std::ref(data));if (th.joinable()) {th.join();
}

使用join的优势在于,只要子线程不退出,主线程不会退出。这意味什么?一是线程被阻塞,二是一旦有编码问题,整个程序无法正常结束。
5、线程的控制
有的时候需要创建多个线程,而这多个线程可能需要进行集中管理,那么可以使用智能指针进行控制:

#include <iostream>
#include <thread>
#include <memory>
#include <vector>using THREADPTR = std::unique_ptr<std::thread>;class ThreadManager {
public:ThreadManager() {};~ThreadManager() { this->Join(); };ThreadManager(const ThreadManager&) = delete;ThreadManager(const ThreadManager &&) = delete;ThreadManager& operator=(const ThreadManager&) = delete;ThreadManager& operator=(const ThreadManager&&) = delete;int PutThread(THREADPTR tptr) { this->vecThread_.emplace_back(std::move(tptr)); return this->vecThread_.size(); }
private:void Join(){for (auto& th : this->vecThread_){if (th->joinable()){th->join();}}};
private:std::vector<THREADPTR> vecThread_;
};
void ThreadManagerTest() {ThreadManager tm;std::unique_ptr<std::thread> p1 = std::make_unique<std::thread>([&]() {std::cout << "run thread 1!" << std::endl; });std::unique_ptr<std::thread> p2 = std::make_unique<std::thread>([&]() {std::cout << "run thread 2!" << std::endl;; });tm.PutThread(std::move(p1));tm.PutThread(std::move(p2));
}
int main()
{ThreadManagerTest();return 0;
}

三、线程的传参

在线程中传参有两种情况:
1、直接传递
A、通过参数传递(值传递)

int data = 0;
std::thread th = std::thread([&](int d) {std::cout << "this data is:" << d << std::endl;},data);

B、std::ref()传递(引用传递)

int data = 0;
std::thread th = std::thread([&](int d) {std::cout << "this data is:" << d << std::endl;},std::ref(data));

引用传递一定要使用std::ref,否则编译无法通过。

2、隐式传递
即使用Lambada表达式的[]的各种应用方式,前面分析过,这里不再讲解,看一下例程就明白了:

int data = 0;
std::thread th = std::thread([&]() {std::cout << "this data is:" << data << std::endl;});

对一般的开发人员来说,引用传参是比较容易被忽略的,要引起注意。

四、线程的同步

线程的同步这里不做过多的分析,下面会开专门的章节进行分析。其实同步的主要目的还是数据的原因,如果单纯的同步,其实应用场景还是比较少的。那么在多线程之间传递数据或者说共享数据,可以使用几种方式来实现:
1、各种同步机制
如std::mutex,std::condition_variable等,在c++更高版本(11,14,17,20)中还提供了shared_timed_mutex和shared_mutex等。同时还提供了对这些锁的管理类如std::lock_guard等,看一个小例子:

#include <thread>
#include <mutex>
#include <iostream>int g_i = 0;
std::mutex g_i_mutex;  // protects g_ivoid safe_increment()
{std::lock_guard<std::mutex> lock(g_i_mutex);++g_i;std::cout << std::this_thread::get_id() << ": " << g_i << '\n';// g_i_mutex is automatically released when lock// goes out of scope
}int main()
{std::cout << __func__ << ": " << g_i << '\n';std::thread t1(safe_increment);std::thread t2(safe_increment);t1.join();t2.join();std::cout << __func__ << ": " << g_i << '\n';
}

2、原子锁
这个还是比较容易理解的:

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>std::atomic_int acnt;
int cnt;void f()
{for (int n = 0; n < 10000; ++n){++acnt;++cnt;// Note: for this example, relaxed memory order// is sufficient, e.g. acnt.fetch_add(1, std::memory_order_relaxed);}
}int main()
{{std::vector<std::jthread> pool;for (int n = 0; n < 10; ++n)pool.emplace_back(f);}std::cout << "The atomic counter is " << acnt << '\n'<< "The non-atomic counter is " << cnt << '\n';
}

此处的代码使用了C++20中的std::jthread,如果非要简单的来说明这个类,可以理解为封装了std::thread,但在析构时自动调用join函数。不过它还是增加了其它的不少的函数,比如在某些条件下可以对线程进行控制,它的使用相对于std::thread要安全不少。

3、CAS无锁
在c++编程中,无锁编程一般应用在需要并发的数据结构处理中,比如常见的链表、栈等,看下面例子:

#include <atomic>template<typename T>
struct node
{T data;node* next;node(const T& data) : data(data), next(nullptr) {}
};template<typename T>
class stack
{std::atomic<node<T>*> head;
public:void push(const T& data){node<T>* new_node = new node<T>(data);// 将 head 的当前值放到 new_node->next 中new_node->next = head.load(std::memory_order_relaxed);// 现在令 new_node 为新的 head ,但如果 head 不再是// 存储于 new_node->next 的值(某些其他线程必须在刚才插入结点)// 那么将新的 head 放到 new_node->next 中并再尝试while(!head.compare_exchange_weak(new_node->next, new_node,std::memory_order_release,std::memory_order_relaxed)); // 循环体为空// 注意:上述使用至少在这些版本不是线程安全的
// 先于 4.8.3 的 GCC(漏洞 60272),先于 2014-05-05 的 clang(漏洞 18899)
// 先于 2014-03-17 的 MSVC(漏洞 819819)。下面是变通方法:
//      node<T>* old_head = head.load(std::memory_order_relaxed);
//      do
//      {
//          new_node->next = old_head;
//      }
//      while (!head.compare_exchange_weak(old_head, new_node,
//                                         std::memory_order_release,
//                                         std::memory_order_relaxed));}
};int main()
{stack<int> s;s.push(1);s.push(2);s.push(3);
}

无锁编程并非无锁,只是把锁下移到了硬件控制中,它的优势在于线程不睡眠,有速度优势;缺点是也是不睡眠,浪费CPU。另外CAS无锁编程还有一个ABA现象,一定要引起注意。所以其应用场景也比较明显,需要数据吞吐量大,最典型的就是股票行情应用上。如果数据单一读或写数据量大,或者都不多大,使用CAS并没有优势,可能还会有劣势。所以还是那句话,没有最好,只有最合适。

4、特殊情况下的数据保护
如在一些单实例之类的单次应用中,可以使用std::call_once配合 std::one_flag一起来使用。同时,也可以使用局部静态变量等方式来实现数据在多线程的一次性实现。这类应用在网上和书上资料非常多,此处就不举例了。

五、总结

本系列重点是如何写多线程的应用,具体到一些细节的应用,大家需要自己回头多看看相关资料或者书籍资料。如果有一些特别需要说明的,可能会在后面插入一些说明的篇章,但也不会安排在这个应用系列中。也就是说,这里更侧重是对基础知识的应用,在应用前,要把应用的基础点都说明一下。

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

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

相关文章

docker学习(十五、Dockerfile之python示例)

文章目录 一、python的Dockerfile二、Dockerfile分析总结 一、python的Dockerfile # 使用官方的 Python 作为基础镜像 FROM python:3.8# 将工作目录设置为 /app WORKDIR /app# 将当前目录下的所有文件复制到工作目录 /app 下 COPY . /app# 安装所需的依赖库 RUN pip install -r…

机器学习-数学学习汇总

***I数学只是一个工具&#xff0c;会使用&#xff0c;能解决问题就可以了&#xff0c;精确例如到3.14够用就可以了*** 微积分作用&#xff1a;解决非线性问题 学习&#xff1a;27分。 高中数学&#xff1a; 1.高中数学所有知识点表格总结&#xff0c;高中知识点一个不漏&am…

RLHF对LLM泛化性和多样性的影响

paper&#xff1a;Understanding the effects of RLHF on LLM generalisation and diversity 0 背景知识 标准的RLHF finetuning pipeline一般包含3个阶段&#xff1a; supervised fine-tuning (SFT)。对预训练的模型进行用language modeling的方式进行微调。reward modelin…

C++ 之函数重写

在C中&#xff0c;函数重写&#xff08;Function Overriding&#xff09;是一种面向对象编程的概念&#xff0c;它允许派生类重新定义基类中已经定义的虚函数。函数重写用于实现多态性&#xff0c;使得程序能够在运行时选择调用哪个版本的函数&#xff0c;以便适应不同的对象类…

【湖仓一体尝试】MYSQL和HIVE数据联合查询

爬了两天大大小小的一堆坑&#xff0c;今天把一个简单的单机环境的流程走通了&#xff0c;记录一笔。 先来个完工环境照&#xff1a; mysqlhadoophiveflinkicebergtrino 得益于IBM OPENJ9的优化&#xff0c;完全启动后的内存占用&#xff1a; 1&#xff09;执行联合查询后的…

AI时代Python量化交易实战:ChatGPT引领新时代

文章目录 《AI时代Python量化交易实战&#xff1a;ChatGPT让量化交易插上翅膀》关键点内容简介作者简介购买链接 《AI时代架构师修炼之道&#xff1a;ChatGPT让架构师插上翅膀》关键点内容简介作者简介 赠书活动 《AI时代Python量化交易实战&#xff1a;ChatGPT让量化交易插上翅…

登录界面UI开发 - 登录注册开发入门(1)

登录页面 UI 设计教程&#xff1a;多八多 Ai 助手的实战演示 第一课&#xff1a;设计UI界面 欢迎来到多八多Ai应用开发助手登录页面开发教程&#xff01;本教程由讲师小多主讲&#xff0c;将教授如何使用多八多Ai应用开发助手设计登录页面的用户界面&#xff08;UI&#xff09…

Python深度学习028:神经网络模型太多,傻傻分不清?

文章目录 深度学习网络模型常见CNN网络深度学习网络模型 在深度学习领域,有许多常见的网络模型,每种模型都有其特定的应用和优势。以下是一些广泛使用的深度学习模型: 卷积神经网络(CNN): 应用:主要用于图像处理,如图像分类、物体检测。 特点:利用卷积层来提取图像特…

最近怎么流量涨这么多?那我开始讲Hive特性了!

Hive架构原理 a.用户接口&#xff1a;Client CLI&#xff08;Hive shell&#xff09;、JDBC/ODBC(java访问hive)、Hive WEBUI&#xff08;浏览器访问hive&#xff09;和Thrift服务器 b.驱动器&#xff1a;Driver解析器&#xff08;SQL Parser&#xff09;&#xff1a;将SQL字…

pvk2pfx.exe makecert.exe 文件路径

文件路径 C:\Program Files (x86)\Windows Kits\10\bin\XXXXX\x86

GBASE南大通用数据库在Windows和Linux中创建数据源

Windows 中数据源信息可能存在于两个地方&#xff1a;在 Windows 注册表中&#xff08;对 Windows 系统&#xff09;&#xff0c; 或在一个 DSN 文件中&#xff08;对任何系统&#xff09;。 如果信息在 Windows 注册表中&#xff0c;它叫做“机器数据源”。它可能是一个“用 …

产品原型设计软件 Axure RP 9 mac支持多人写作设计

axure rp 9 mac是一款产品原型设计软件&#xff0c;它可以让你在上面任意构建草图、框线图、流程图以及产品模型&#xff0c;还能够注释一些重要地方&#xff0c;axure rp汉化版可支持同时多人写作设计和版本管理控制&#xff0c;这款交互式原型设计工具可以帮助设计者制作出高…

软件工程中关键的图-----知识点总结

目录 1.数据流图 2.变换型设计和事务型设计 3.程序流程图 4.NS图和PAD图&#xff1a; 5.UML图 1.用例图 2.类图 3.顺序图 4.协作图 本文为个人复习资料&#xff0c;包含个人复习思路&#xff0c;多引用&#xff0c;也想和大家分享一下&#xff0c;希望大家不要介意~ …

PCL配置记录

PCL配置记录 1. Windows10vs2019pcl win10vs2019pcl 1.11.1 1.下载与安装 https://github.com/PointCloudLibrary/pcl/releases ) 双击exe安装 注意&#xff1a; ( ) 解压 “pcl-1.11.0-pdb-msvc2019-win64.zip”&#xff0c;将解压得到的文件夹中的内容添加“…\PCL…

基于JAVA的厦门旅游电子商务预订系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 景点类型模块2.2 景点档案模块2.3 酒店管理模块2.4 美食管理模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 学生表3.2.2 学生表3.2.3 学生表3.2.4 学生表 四、系统展示五、核心代码5.1 新增景点类型5.2 查询推荐的…

TYPE C 接口知识详解

1、Type C 概述 Type-C口有4对TX/RX分线&#xff0c;2对USBD/D-&#xff0c;一对SBU&#xff0c;2个CC&#xff0c;另外还有4个VBUS和4个地线。 当Type-C接口仅用作传输DP信号时&#xff0c;则可利用4对TX/RX&#xff0c;从而实现4Lane传输&#xff0c;这种模式称为DPonly模式…

android 9 adb安装过程学习(四)覆盖安装

六、PackageManagerService.replacePackageLIF - 覆盖安装 一、参数分析 位置&#xff1a;"frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java" 这里我们来回顾下传入的参数&#xff1a;final int policyFlags 就是我们之前的…

背包小专题

背包小专题 1. CF106C Buns题目描述题目概况思路点拨代码实现 2. CF864E Fire题目描述题目概况思路点拨代码实现 3. CF366C Dima and Salad题目描述题目概况思路点拨背包瓶颈解决方法 代码实现 4. CF1132E Knapsack题目描述题目概况思路点拨代码实现 5. CF632E Thief in a Shop…

Leetcode 435 无重叠区间

题意理解&#xff1a; 给定一个区间的集合 intervals 要求需要移除区间&#xff0c;使剩余区间互不重叠 目标&#xff1a;最少需要移除几个区间。 解题思路&#xff1a; 采用贪心思路解题&#xff0c;什么是全局最优解&#xff0c;什么是局部最优解。 全局最优解&#xff0c;删…

使用Java语言判断一个年度是不是闰年

一、 代码说明 引入Scanner函数&#xff0c;将类命名为Judge类&#xff0c;使用try语句和catch语句将整体代码包围起来&#xff0c;使用if语句来判断是否为闰年&#xff0c;输入年份&#xff0c;然后得到相应的结论。 二、代码 import java.util.Scanner; public class Judg…