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

news2024/11/22 20:16:31

一些小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 500000
    thread *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 batch
    return 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 500000
    thread *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(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 500000
    thread *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 500000
    ThreadPool 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 batch
    return 0;
}
ENDS(thread_pool_test)

int main() {
    thread_pool_test::main();
    return 0;
}

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

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

相关文章

【python环境搭建】一台电脑下安装不同python版本时如何安装模块

我的环境中安装了2个版本的python&#xff1a; 一个时Anaconda的 一个是python3.10 多个版本python的安装 卸载 pip使用 详细方法可以看这个贴子 Windows环境同时安装多个版本的Python解释器 pip的使用 安装package pip install [package] 更新package pip install [pack…

下属不服管,当众顶撞自己怎么办?

作为领导者&#xff0c;面对下属公然顶撞自己&#xff0c;是一种不尊重和不合适的行为&#xff0c;可能会让你感到失望和困惑。然而&#xff0c;在处理这一情况时&#xff0c;你可以采取以下措施来妥善处理&#xff1a; 保持冷静&#xff1a;尽管受到了侮辱和指责&#xff0c;…

怎么获取别人店铺的商品呢?

jd.item_search_shop(获得店铺的所有商品) 为了进行电商平台 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个JD应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载JDAPI的SDK并掌握基本的API…

【脑机接口论文与代码】 基于自适应FBCCA的脑机接口控制机械臂

Brain-Controlled Robotic Arm Based on Adaptive FBCCA 基于自适应FBCCA的脑机接口控制机械臂论文下载&#xff1a;算法程序下载&#xff1a;摘要1 项目介绍2 方法2.1CCA算法2.2FBCCA 算法2.3自适应FBCCA算法 3数据获取4结果4.1脑地形图4.2频谱图4.3准确率 5结论 基于自适应FB…

【C++】常用算术生成算法

0.前言 1.accumulate #include <iostream> using namespace std;// 常用算术生成算法 #include<vector> #include<numeric> //accumulate 的调用头文件void test01() {vector<int>v;for (int i 0; i < 100; i){v.push_back(i);}int total accumu…

msvcp140.dll是什么东西,如何解决msvcp140.dll丢失的问题的方法分享

在现代生活中&#xff0c;电脑已经成为我们工作、学习和娱乐的重要工具。然而&#xff0c;电脑问题的出现往往会给我们的生活带来不便。其中&#xff0c;"msvcp140.dll丢失"是一个常见的电脑问题。本文将详细介绍这个问题的原因和解决方法&#xff0c;帮助大家更好地…

python爬虫经典实例(二)

在前一篇博客中&#xff0c;我们介绍了五个实用的爬虫示例&#xff0c;分别用于新闻文章、图片、电影信息、社交媒体和股票数据的采集。本文将继续探索爬虫的奇妙世界&#xff0c;为你带来五个全新的示例&#xff0c;每个示例都有其独特的用途和功能。 1. Wikipedia数据采集 爬…

C++之编译时预定义宏flag(二百一十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Bean拷贝组件(注解驱动)方案设计与落地

一、背景 数据流转在各层之间的过程&#xff0c;应当是改头换面的&#xff0c;字段属性数量&#xff0c;属性名称&#xff08;一般不变&#xff0c;但也有重构时出现变化的情况&#xff09;&#xff0c;类型名称&#xff08;普遍变化例如BO、VO、DTO&#xff09;。对于转换的业…

华为OD机试 - 字符串加密(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

企业架构LNMP学习笔记45

失效机制&#xff08;了解&#xff09; 1&#xff09;如果key过期了&#xff0c;value会及时删除么&#xff1f;空间会及时清理么&#xff1f; 2&#xff09;如果分配的存储空间&#xff0c;写满了&#xff0c;还允许写么&#xff1f; -m可以配置内存大小。 memcached 内部不…

React TypeScript | 快速了解 antd 的使用

1. 安装&#xff1a; 就像安装其他插件库一样&#xff0c;在项目文件夹下执行&#xff1a; npm install antd --save如果你安装了 yarn&#xff0c;也可以执行&#xff1a; yarn add antd2. 引用 import { Button, Tooltip } from "antd"; import "antd/dis…

Linux内核—模块编译方法

一、向内核添加新功能 1.1 静态加载法&#xff1a; 即新功能源码与内核其它代码一起编译进uImage文件内 新功能源码与Linux内核源码在同一目录结构下 在linux-3.14/driver/char/目录下编写myhello.c&#xff0c;文件内容如下&#xff1a; #include <linux/module.h> #i…

AWS创建实例 启用/禁用 自动分配公有 IP

给AWS新账户做完了对等连接&#xff0c;因为默认VPC网段都冲突 就换了VPC&#xff0c;然后发现新VPC内创建的实例都没有分配公网IP地址&#xff0c;自动分配公网IP地址变成了禁用。后续建机子需要手动修改成启用太麻烦了。 在VPC里面找到编辑子网设置&#xff0c;勾上启用自动…

不使用辅助变量的前提下实现两个变量的交换

package operator; //不用第三个辅助变量&#xff0c;实现两个数的交换 public class Demo08 {public static void change(int a, int b){a ab;b a-b;a a-b;System.out.println(a);System.out.println(b);}public static void main(String[] args) {change(900,3000);} }后续…

IDEA2023.2.1取消空包隐藏,切换包结构(Compact Middle Packages)

解决2023版idea的包结构 取消勾选即可。 取消勾选Compact Middle Packages选项后&#xff0c;再创建包时&#xff0c;即可自动创建树形结构。 仅供学习使用&#xff01;

Matlab图像处理-三基色

三基色 在计算机中&#xff0c;显示器的任何颜色&#xff08;全色域&#xff09;都可以由红、绿、蓝三种颜色组成&#xff0c;称为三基色。 三基色的原理 各原色的取值范围为0~255。 任何颜色都可以与这3种颜色以不同的比例混合获得&#xff0c; 这就是三基色的原理。 在计算…

VMWare虚拟机扩容并挂载磁盘

零、说在前面 我们在使用在VMWare创建虚机运行系统的时候&#xff0c;难免会因为前期规划不足而遇到磁盘空间被占满的情况&#xff0c;此时就需要对虚机的原有存储空间进行扩容。而整体思路&#xff0c;就是将新追加的磁盘空间归属到逻辑卷下&#xff08;类似window的给磁盘分区…

【深度学习】 Python 和 NumPy 系列教程(十七):Matplotlib详解:2、3d绘图类型(3)3D条形图(3D Bar Plot)

目录 一、前言 二、实验环境 三、Matplotlib详解 1、2d绘图类型 2、3d绘图类型 0. 设置中文字体 1. 线框图 2. 3D散点图 3. 3D条形图&#xff08;3D Bar Plot&#xff09; 一、前言 Python是一种高级编程语言&#xff0c;由Guido van Rossum于1991年创建。它以简洁、易读…

vue的由来、vue教程和M-V-VM架构思想、vue的使用、nodejs

vue vue的由来 vue教程和M-V-VM架构思想 vue的使用 nodejs vue的由来 # 1 HTML(5)、CSS(3)、JavaScript(ES5、ES6、ES11)&#xff1a;编写一个个的页面 -> 给后端(PHP、Python、Go、Java) -> 后端嵌入模板语法 -> 后端渲染完数据 -> 返回数据给前端 -> 在浏览…