C++多线程的用法(包含线程池小项目)

一些小tips: 

编译命令如下:

 g++ 7.thread_pool.cpp -lpthread

查看运行时间:

time ./a.out

 获得本进程的进程id:

this_thread::get_id()

 需要引入的库函数有:

#include<thread> // 引入线程库
#include<mutex> // 加入锁机制需要引入库函数mutex
#include<condition_variable> // 引入信号量机制

定义信号量、锁:

condition_variable m_cond
std::mutex m_mutex;

所谓的多线程只不过就是指定的某一个函数为入口函数,的另外一套执行流程。

什么是临界资源?多线程情况下,大家都能访问到的资源。

进程是资源分配的最基本单位,线程是进程中的概念。

线程也是操作系统分配的一批资源。一个线程的栈所占用的空间有多大?——8M。查看命令:

ulimit -a

简单用法:

#include<iostream>
#include<thread>
using namespace std;#define BEGINS(x) namespace x{
#define ENDS(x) }BEGINS(thread_usage)
void func() {cout << "hello wolrd" << endl;return ;
}
int main() {thread t1(func);  // t1已经开始运行了t1.join();        // 等待t1线程结束return 0;
}
ENDS(thread_usage)int main() {thread_usage::main();return 0;
}

那么如何给入口函数传参呢? 在C++中就很简单:

void print(int a, int b) {cout << a << " " << b << endl;return ;
}
int main() {thread t2(print, 3, 4);t2.join();return 0;
}

多线程封装思维:在相应的功能函数内部,不要去访问全局变量,类似于一个原子操作的功能。本身就支持多线程并行。

多线程程序设计:线程功能函数和入口函数要分开。(高内聚低耦合)

能不用锁就不用锁,因为这个锁机制会给程序运行效率带来极大的影响。

实现素数统计的功能

不加锁版:

#include<iostream>
#include<thread>
#include<cmath>
#include<mutex>
using namespace std;#define BEGINS(x) namespace x{
#define ENDS(x) }BEGINS(is_prime)
bool is_prime(int x) {for (int i = 2, I = sqrt(x); i <= I; i++) {if (x % i == 0) return false;}return true;
}
// 多线程——功能函数 
int prime_count(int l, int r) {// 从l到r范围内素数的数量int ans = 0;for (int i = l; i <= r; i++) {ans += is_prime(i);}return ans;
}
// 多线程——入口函数 
void worker(int l, int r, int &ret) {cout << this_thread::get_id() << "begin" << endl;ret = prime_count(l, r);cout << this_thread::get_id() << "dnoe" << endl;
}
int main() {#define batch 500000thread *t[10];int ret[10];for (int i = 0, j = 1; i < 10; i++, j += batch) {t[i] = new thread(worker, j, j + batch - 1, ref(ret[i]));}for (auto x : t) x->join();int ans = 0;for (auto x : ret) ans += x;for (auto x : t) delete x;cout << ans << endl;#undef batchreturn 0;
}
ENDS(is_prime)int main() {// thread_usage::main();is_prime::main();return 0;
}

加锁版:

#include<iostream>
#include<thread>
#include<cmath>
#include<mutex>
using namespace std;#define BEGINS(x) namespace x{
#define ENDS(x) }
BEGINS(prime_count2) 
int ans = 0;
std::mutex m_mutex;
bool is_prime(int x) {for (int i = 2, I = sqrt(x); i <= I; i++) {if (x % i == 0) return false;}return true;
}
void prime_count(int l, int r) {cout << this_thread::get_id() << " begin\n";for (int i = l; i <= r; i++) {std::unique_lock<std::mutex> lock(m_mutex); // 临界区ans += is_prime(i);lock.unlock();    }cout << this_thread::get_id() << " done\n";return ;
}
int main() {#define batch 500000thread *t[10];for (int i = 0, j = 1; i < 10; i++, j += batch) {t[i] = new thread(prime_count, j, j + batch - 1);}for (auto x : t) x->join();for (auto x : t) delete x;cout << ans << endl;#undef batchreturn 0;
}
ENDS(prime_count2)int main() {prime_count2::main();return 0;
}

为什么不用++而是用+=

因为后者是原子操作,而前者不是,在多线程情况下可能存在覆盖写的问题。

__sync_fetch_and_add

这个函数也是原子操作。

#include<iostream>
#include<thread>
#include<cmath>
#include<mutex>
using namespace std;#define BEGINS(x) namespace x{
#define ENDS(x) }BEGINS(thread3)
int ans = 0;
bool is_prime(int x) {for (int i = 2, I = sqrt(x); i <= I; i++) {if (x % i == 0) return false;}return true;
}
void prime_count(int l, int r) {cout << this_thread::get_id() << "begin\n";for (int i = l; i <= r; i++) {int ret = is_prime(i);__sync_fetch_and_add(&ans, ret);}cout << this_thread::get_id() << "done\n";
}
int main() {#define batch 500000thread *t[10];for (int i = 0, j = 1; i < 10; i++, j += batch) {t[i] = new thread(prime_count, j, j + batch - 1);}for (auto x : t) x->join();for (auto x : t) delete x;cout << ans << endl;#undef batch return 0;
}
ENDS(thread3)int main() {thread3::main();return 0;
}

根据你提供的输出结果 `./a.out 2.06s user 0.02s system 385% cpu 0.538 total`,可以对各项含义进行解释:

  • - `2.06s user`:表示程序在用户态消耗的CPU时间为2.06秒。这是程序执行期间用于执行用户代码的时间。
  • - `0.02s system`:表示程序在内核态消耗的CPU时间为0.02秒。这是程序执行期间用于执行内核代码的时间。
  • - `385% cpu`:表示程序使用了385%的CPU资源。这个值超过100%是因为程序可能在多个CPU核心上并行执行。所以 总时间 < 用户态+内核态时间 是非常正常的。
  • - `0.538 total`:表示程序从开始执行到结束的总时间为0.538秒。这是包括了实际执行时间、等待时间和其他非CPU消耗的时间。

综上所述,这个输出结果告诉你程序的执行时间情况。用户态和内核态的CPU时间可以帮助你了解程序在不同执行环境中的资源消耗情况。CPU使用率告诉你程序在执行期间使用了多少CPU资源。总时间则是程序的整体执行时间。

线程池的实现

我们希望申请内存的动作是可控的。线程作为一种内存资源,在通常的设计模式下,申请线程资源并不可控。

解决办法就是线程池。在线程池内部,线程数量是可控的。把计算任务打包,扔到一个任务队列中。

什么是计算任务?——分为过程(函数方法)和数据(函数参数),如何进行打包——bind()方法。

线程池解决了传统的多线程程序设计中,面对不同任务我们需要实现不同程序的麻烦:而是直接往里塞任务就行了。这样就实现了资源的有效利用和管控。

m_cond.notify_one() 对于这个进程来说,是否同时也解锁了互斥资源?

是的,m_cond.notify_one()会解锁互斥资源。在调用m_cond.notify_one()之前,通常会先调用m_mutex.lock()来锁定互斥资源,然后在适当的时候调用m_cond.notify_one()来唤醒等待该条件的线程。在唤醒线程后,该线程会重新尝试获取互斥资源的锁,而其他等待的线程会继续等待。所以,m_cond.notify_one()不仅唤醒等待的线程,还会解锁互斥资源,使得等待的线程有机会获取互斥资源的锁。

子进程的中wait函数对互斥量进行解锁,同时线程进入阻塞或者等待状态。

/*************************************************************************> File Name: threadpool.cpp> Author: jby> Mail: > Created Time: Wed 13 Sep 2023 08:48:23 AM CST************************************************************************/#include<iostream>
#include<cmath>
#include<vector>
#include<unordered_map>
#include<queue>
#include<mutex>
#include<thread>
#include<condition_variable>
#include<functional>
using namespace std;#define BEGINS(x) namespace x {
#define ENDS(x) }BEGINS(thread_pool_test)
class Task {
public:template<typename FUNC_T, typename ...ARGS>Task(FUNC_T func, ARGS ...args) {this->func = bind(func, forward<ARGS>(args)...);}void run() {func();return ;}
private:function<void()> func; // 任意函数
};
class ThreadPool {
public:ThreadPool(int n = 1) : thread_size(n), threads(n), starting(false) {this->start();return ;}void worker() {auto id = this_thread::get_id(); // 获得本进程号running[id] = true;while (running[id]) {// 取任务Task *t = get_task(); t->run();delete t;}return ;}void start() {if (starting == true) return ; // 如果已经开始了就不用启动了for (int i = 0; i < thread_size; i++) {threads[i] = new thread(&ThreadPool::worker, this);}starting = true;return ;}template<typename FUNC_T, typename ...ARGS>void add_task(FUNC_T func, ARGS ...args) {unique_lock<mutex> lock(m_mutex); tasks.push(new Task(func, forward<ARGS>(args)...)); // 任务池相当于临界资源m_cond.notify_one(); // 生产者消费者模型return ;}void stop() {if (starting == false) return ; // 如果已经关了就不用再关了for (int i = 0; i < threads.size(); i++) { // 往队列末尾投递毒药任务this->add_task(&ThreadPool::stop_runnnig, this);}for (int i = 0; i < threads.size(); i++) {threads[i]->join();}for (int i = 0; i < threads.size(); i++) {delete threads[i]; // 释放那片进程剩余的空间threads[i] = nullptr; // 进程指针指向空}starting = false;return ;}~ThreadPool() {this->stop();while (!tasks.empty()) { // 如果任务队列里还有任务没执行完,全部丢弃delete tasks.front();tasks.pop();}return ;}
private:bool starting;int thread_size;Task *get_task() {unique_lock<mutex> lock(m_mutex);while (tasks.empty()) m_cond.wait(lock); // 生产者消费者模型 //子进程的中wait函数对互斥量进行解锁,同时线程进入阻塞或者等待状态。Task *t = tasks.front();tasks.pop();return t;}std::mutex m_mutex;std::condition_variable m_cond;vector<thread *> threads; // 线程池子unordered_map<decltype(this_thread::get_id()), bool> running; // 进程号到运行状态的哈希映射queue<Task *> tasks;  // 任务队列void stop_runnnig() { // 毒药任务auto id = this_thread::get_id();running[id] = false;return ;}
};
bool is_prime(int x) {for (int i = 2, I = sqrt(x); i <= I; i++) {if (x % i == 0) return false;}return true;
}
int prime_count(int l, int r) {int ans = 0;for (int i = l; i <= r; i++) {ans += is_prime(i);}return ans;
}
void worker(int l, int r, int &ret) {cout << this_thread::get_id() << "begin\n";ret = prime_count(l, r);cout << this_thread::get_id() << "done\n";return ;
}
int main() {#define batch 500000ThreadPool tp(5); // 五个任务的窗口队列int ret[10];for (int i = 0, j = 1; i < 10; i++, j += batch) {tp.add_task(worker, j, j + batch - 1, ref(ret[i]));}tp.stop();int ans = 0;for (auto x : ret) ans += x;cout << ans << endl;#undef batchreturn 0;
}
ENDS(thread_pool_test)int main() {thread_pool_test::main();return 0;
}

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

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

相关文章

Ui自动化测试上传文件方法都在这里了 ~

前言 实施UI自动化测试的时候&#xff0c;经常会遇见上传文件的操作&#xff0c;那么对于上传文件你知道几种方法呢&#xff1f;今天我们就总结一下几种常用的上传文件的方法&#xff0c;并分析一下每个方法的优点和缺点以及哪种方法效率&#xff0c;稳定性更高 被测HTML代码…

睿趣科技:抖音开店前期需要准备什么

抖音作为全球最受欢迎的短视频平台之一&#xff0c;已经成为了许多年轻人的创业和赚钱的机会。如果你计划在抖音上开店&#xff0c;那么在正式开业之前&#xff0c;有一些重要的准备工作是必不可少的。下面就是抖音开店前期需要准备的关键步骤和注意事项。 确定你的目标和产品&…

Matlab图像处理-三原色

三原色 根据详细的实验结果&#xff0c;人眼中负责颜色感知的细胞中约有65%对红光敏感&#xff0c;33%对绿光敏感&#xff0c;只有2%对蓝光敏感。正是人眼的这些吸收特性决定了所看到的彩色是一般所谓的原色红&#xff08;R&#xff09;、绿&#xff08;G&#xff09;和蓝&…

动态渲染 echarts 饼图(vue 2 + axios + Springboot)

目录 前言1. 项目搭建1.1. 前端1.2. 后端 2. 后端数据渲染前端2.1 补充1&#xff1a;在 vue 中使用 axios2.2. 补充2&#xff1a;Springboot 处理跨域问题2.3. 修改前端代码2.3.1 修改饼图样式2.3.2 调用后台数据渲染饼图2.3.3 改造成内外两个圈 前言 因为上文中提到的需求就是…

内网隧道代理技术(二十五)之 ICMP隧道反弹SHELL

ICMP隧道反弹SHELL ICMP隧道原理 由于ICMP报文自身可以携带数据,而且ICMP报文是由系统内核处理的,不占用任何端口,因此具有很高的隐蔽性。把数据隐藏在ICMP数据包包头的data字段中,建立隐蔽通道,可以实现绕过防火墙和入侵检测系统的阻拦。 ICMP隧道有以下的优点: ICMP…

腾讯云4核8G服务器选CVM还是轻量比较好?价格对比

腾讯云4核8G云服务器可以选择轻量应用服务器或CVM云服务器标准型S5实例&#xff0c;轻量4核8G12M服务器446元一年&#xff0c;CVM S5云服务器935元一年&#xff0c;相对于云服务器CVM&#xff0c;轻量应用服务器性价比更高&#xff0c;轻量服务器CPU和CVM有区别吗&#xff1f;性…

博客系统(升级(Spring))(四)(完)基本功能(阅读,修改,添加,删除文章)(附带项目)

博客系统 (三&#xff09; 博客系统博客主页前端后端个人博客前端后端显示个人文章删除文章 修改文章前端后端提取文章修改文章 显示正文内容前端后端文章阅读量功能 添加文章前端后端 如何使用Redis项目地点&#xff1a; 博客系统 博客系统是干什么的&#xff1f; CSDN就是一…

数字化转型对企业有哪些优势?

数字化转型为企业提供了众多优势&#xff0c;帮助他们在日益数字化的世界中保持竞争力、敏捷性和响应能力。以下是一些主要优势&#xff1a; 1.提高效率和生产力&#xff1a; 重复性任务和流程的自动化可以减少人为错误&#xff0c;并使员工能够专注于更具战略性的任务。简化…

Apache Linki 1.3.1+DataSphereStudio+正常启动+微服务+端口号

我使用的是一键部署容器化版本&#xff0c;官方文章 默认会启动6个 Linkis 微服务&#xff0c;其中下图linkis-cg-engineconn服务为运行任务才会启动,一共七个 LINKIS-CG-ENGINECONN:38681 LINKIS-CG-ENGINECONNMANAGER:9102 引擎管理服务 LINKIS-CG-ENTRANCE:9104 计算治理入…

Vue开发小注意点

改bug 更改了配置项啥的&#xff0c;保存刷新发现没变&#xff0c;那就重启项目&#xff01;&#xff01;&#xff01;&#xff01; binding.value 和 e.target.value binding.value Day5 指令的值 e.target.value Day4 表单组件封装 binding.value 和 e.target.valu…

plt函数显示图片 在图片上画边界框 边界框坐标转换

一.读取图片并显示图片 %matplotlib inline import torch from d2l import torch as d2l读取图片 image_path ../data/images/cat_dog_new.jpg # 创建画板 figure d2l.set_figsize() image d2l.plt.imread(image_path) d2l.plt.imshow(image);二.给出一个(x左上角,y左上角,…

使用Git把项目上传到Gitee的详细步骤

1.到Git官网下载并安装 2.到Gitee官网进行注册&#xff0c;然后在Gitee中新建一个远程仓库 3.设置远程仓库的参数 4.返回Gitee查看仓库是否生成成功 5.新建一个文件夹作为你的本地仓库 6.将新建好的文件夹初始化成本地仓库 第一步&#xff1a;右键点击刚创建的本地仓库&#…

小程序实现一个 倒计时组件

小程序实现一个 倒计时组件 需求背景 要做一个倒计时&#xff0c;可能是天级别&#xff0c;也可能是日级别&#xff0c;时级别&#xff0c;而且每个有效订单都要用&#xff0c;就做成组件了 效果图 需求分析 需要一个未来的时间戳&#xff0c;或者在服务度直接下发一个未来…

NeuroFlash:AI文章写作与生成工具

【产品介绍 ​ 】 名称 NeuroFlash 上线时间 2015 具体描述 Neuroflash是一款基于人工智能的文本和图像生成器&#xff0c;可以帮助用户快速创建高质量的内容。Neuroflash拥有超过100种短文和长文的文本类型&#xff0c;涵盖了各种营销场景和需求。只需要输入简单的指示&#…

最新ChatGPT网站源码+支持GPT4.0+支持Midjourney绘画+支持国内全AI模型

一、智能创作系统 SparkAi创作系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧&…

Debian12系统下LAMP环境中Nubuilder4.5的安装

一、环境搭建 按照官方的说法&#xff0c;Apache2和Nginx都可以的&#xff0c;实际上&#xff0c;你最好直接按照 Mariadb\Apache2\Php8.2 这个顺序&#xff0c;搭建LAMP环境较好。不然各种调试&#xff0c;还不一定能够成功。 相关搭建方法&#xff0c;属于一般操作&#xf…

js中如何判断一个变量是否为数字类型?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐使用Number.isNaN()方法⭐使用正则表达式⭐使用isNaN()函数⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个…

C++ - map 的 例题

前言 本博客在 一下文章关于 map 和 set 讲解之下&#xff0c;对 map 当中的 operator[] &#xff08;&#xff09;函数的功能运用&#xff0c;感受 map 功能强大。 349. 两个数组的交集 - 力扣&#xff08;LeetCode&#xff09; 给定两个数组 nums1 和 nums2 &#xff0c;返回…

AWT中常用组件

笔记&#xff1a;https://www.yuque.com/huangzhanqi/rhwoir/repuodh23fz01wiv 仓库&#xff1a;Java图形化界面: Java图形化界面学习demo与资料 (gitee.com) 基本组件 组件名 功能 Button Button Canvas 用于绘图的画布 Checkbox 复选框组件&#xff08;也可当做单选…

Java 使用 EMQX 实现物联网 MQTT 通信

一、介绍 1、MQTT MQTT(Message Queuing Telemetry Transport, 消息队列遥测传输协议)&#xff0c;是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议&#xff0c;该协议构建于TCP/IP协议上&#xff0c;由IBM在1999年发布。MQTT最大优点在于&#xff…