c++多线程(一)线程管理

news2024/12/30 2:03:25

来源:微信公众号「编程学习基地」

文章目录

      • 1.启动线程
      • 2.等待线程完成
        • 2.1特殊情况下的等待
        • 2.2使用RAII等待线程完成
        • 2.3后台运行线程
        • 2.4量产线程,等待结束
      • 2.传递参数
      • 3.转移线程所有权
      • 4.运行时决定线程数量
      • 2.5 识别线程

1.启动线程

当把函数对象传入到线程构造函数中时,需要避免语法解析问题,不能直接传入函数,而是需要传入函数地址

#include<iostream>
#include<thread>
using namespace std;
void thread_fun(){
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}

class workThread{
    public:
    workThread(){}
    void operator()() 
    {
        cout<<"[2]thread Id:"<<std::this_thread::get_id()<<endl;
    }
};

int main(){
    thread thr(thread_fun);
    workThread workthr2;
    /**
     * @brief 线程类启动线程的三种方式
     */
    // thread thr2(workthr2);
    // thread thr2((workThread()));
    thread thr2{workThread()};
    thr2.join();
    thr.join();
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    return 0;
}

2.等待线程完成

std::thread实例需要使用join(),可以确保局部变量在线程完成后被销毁。join()是简单粗暴的等待线程完成或不等待。只能对一个线程使用一次join();一旦已经使用过join()std::thread对象就不能再次加入了,当对其使用joinable()时,将返回false)。

#include<iostream>
#include<thread>
using namespace std;
void thread_fun(){
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}
int main(){
    thread thr(thread_fun);
    if(thr.joinable())
    {
        thr.join();
    }
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    return 0;
}

2.1特殊情况下的等待

需要对一个还未销毁的std::thread对象使用join()detach()。如果想要分离一个线程,可以在线程启动后,直接使用**detach()**进行分离。如果打算等待对应线程,需要选择一个合适的位置。例如当出现异常并抛出异常后未正确调用线程等待,需要在异常处理过程中调用异常。

void f()
{
  int some_local_state=0;
  func my_func(some_local_state);
  std::thread t(my_func);	//开启线程
  try
  {
    do_something_in_current_thread();	//执行其他的一些任务
  }
  catch(...)	//执行其他任务时出现异常捕获后抛出异常前需要等待线程结束
  {
    t.join();  // 1
    throw;
  }
  t.join();  // 2
}

可看可不看,不重要…

2.2使用RAII等待线程完成

RAII是Resource Acquisition Is Initialization(wiki上面翻译成 “资源获取就是初始化”)的简称,是C++语言的一种管理资源、避免泄漏的惯用法。利用的就是C++构造的对象最终会被销毁的原则。RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。

#include<iostream>
#include<thread>
using namespace std;
class thread_guard
{
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_):
        t(t_)
    {}
    ~thread_guard()
    {
        if(t.joinable())
        {
            t.join();
        }
    }
    thread_guard(thread_guard const&)=delete;
    thread_guard& operator=(thread_guard const&)=delete;
};
void thread_fun(){
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}
int main(){
    thread thr(thread_fun);
    thread_guard guard(thr);
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    return 0;
}

拷贝构造函数和拷贝赋值操作被标记为=delete,是为了不让编译器自动生成它们。直接对一个对象进行拷贝或赋值是危险的,因为这可能会弄丢已经加入的线程。通过删除声明,任何尝试给thread_guard对象赋值的操作都会引发一个编译错误。

2.3后台运行线程

使用detach()会让线程在后台运行,这就意味着主线程不能与之产生直接交互。也就是说,不会等待这个线程结束;如果线程分离,那么就不可能有std::thread对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。

通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,且没有任何用户接口,并在后台运行的线程。

std::thread t(do_background_work);
t.detach();
assert(!t.joinable());

2.4量产线程,等待结束

通过标准中的std::vector<>量产线程并且等待他们结束

#include <iostream>
#include <numeric>
#include <vector>
#include <thread>
#include <functional>   //mem_fn
#include <algorithm>    //for_each
using namespace std;
template<typename Iterator,typename T>
struct accumulate_block
{
    void operator()(Iterator first,Iterator last,T& result)
    {
        result=std::accumulate(first,last,result);
    }
};
struct MyAccumulate
{
    void operator()(vector<int>::iterator begin,vector<int>::iterator end,int& result)
    {
        result=std::accumulate(begin,end,result);
    }
};
void fun(vector<int>::iterator begin,vector<int>::iterator end,int& result){
    result = std::accumulate(begin, end, result);
}
int main(){
    //定义完成任务的线程数量
    int thread_num = 4;
    vector<int> vec;
    //任务:有一个100大小容器,需要统计容器中数量的和
    for(int i=0;i<100;i++)
        vec.push_back(i + 1);
    //计算每个线程需要计算的快大小
    int block_size = 100 / thread_num;
    //统计任务结果集,每个线程统计一个结果
    vector<int> results(thread_num);
    vector<thread> threads(thread_num); 
    vector<int>::iterator block_begin = vec.begin();
    for(int i=0;i<thread_num;i++){
        vector<int>::iterator block_end = block_begin;
        //将迭代器往后移动 block_size 大小
        std::advance(block_end,block_size);
        MyAccumulate tmp;
        // threads[i] = std::thread(accumulate_block<vector<int>::iterator,int>(),block_begin,block_end,std::ref(results[i]));
        threads[i] = std::thread(fun, block_begin, block_end, std::ref(results[i]));
        block_begin = block_end;
    }
    for_each(threads.begin(),threads.end(),std::mem_fn(&thread::join));
    int result = std::accumulate(results.begin(), results.end(), 0);
    cout << "result:" << result <<endl;
}

该示例主要是演示创建多个线程分配任务后对计算结果进行统计。

2.传递参数

因为线程的创建属于函数式编程,所以传递的参数都会被拷贝一份,传入参数时尽可能传入指针

#include<iostream>
#include<thread>
using namespace std;
void thread_fun(int* arg){
    int tmp = *(int*)arg;
    cout<<"thread Id:"<<std::this_thread::get_id()<< ",tmp:"<<tmp << endl;
}
class workThread{
    int m_val;
    public:
    workThread(int val){
        m_val = val;
    }
    void operator()() 
    {
        cout<<"thread Id:"<<std::this_thread::get_id()<<","<<m_val<<endl;
    }
};
int main(){
    int arg = 10;
    thread thr(thread_fun,&arg);
    thr.join();
    workThread workthr2(arg);
    // std::thread thr2(workthr2);
    // std::thread thr2((workThread(arg)));
    std::thread thr2{(workThread(arg))};
    thr2.join();
	return 0;
}

当需要传递引用时,构造函数无视函数期待的参数类型,并盲目的拷贝已提供的变量,可以使用std::ref将参数转换成引用的形式。

#include<iostream>
#include<thread>
using namespace std;
void thread_fun2(int &arg){
    arg = 20;
    cout<<"thread Id:"<<std::this_thread::get_id()<< ",tmp:"<<arg << endl;
}
int main(){
    int arg = 10;
    thread thr(thread_fun2,std::ref(arg));
    thr.join();
    cout << "thread Id:" << std::this_thread::get_id() << ",arg:" << arg << endl;
    return 0;
}
thread Id:139977403291392,tmp:20
thread Id:139977420166976,arg:20

3.转移线程所有权

C++标准库中有很多资源占有(resource-owning)类型,比如std::ifstream,std::unique_ptr还有std::thread都是可移动(movable),但不可拷贝(cpoyable)。这就说明执行线程的所有权可以在std::thread实例中移动。

#include<iostream>
#include<thread>
using namespace std;
void thread_fun2(int &arg){
    arg = 20;
    cout<<"thread Id:"<<std::this_thread::get_id()<< ",tmp:"<<arg << endl;
}
int main(){
    int arg = 10;
    thread thr(thread_fun2,std::ref(arg));
    thread thr2 = std::move(thr);
    if(thr2.joinable()){
        cout<<"thr2 joinable.."<<endl;
        thr2.join();
    }
    cout << "thread Id:" << std::this_thread::get_id() << ",arg:" << arg << endl;
    return 0;
}

当所有权可以在函数内部传递,就允许std::thread实例可作为参数进行传递,std::thread支持移动的好处是可以创建thread_guard类的实例,并且拥有其线程的所有权。

为了确保线程程序退出前完成,下面的代码里定义了thread_guard类

#include <iostream>
#include <thread>
using namespace std;
class thread_guard
{
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_):
        t(t_)
    {
        if(!t.joinable())
            throw std::logic_error("No thread");
    }
    ~thread_guard()
    {
        t.join();
    }
    thread_guard(thread_guard const&)=delete;
    thread_guard& operator=(thread_guard const&)=delete;
};
void thread_fun(){
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}
int main(){
    thread thr(thread_fun);
    thread_guard guard(thr);
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    return 0;
}

4.运行时决定线程数量

std::thread::hardware_concurrency()在新版C++标准库中是一个很有用的函数。这个函数将返回能同时并发在一个程序中的线程数量。例如,多核系统中,返回值可以是CPU核芯的数量。返回值也仅仅是一个提示,当系统信息无法获取时,函数也会返回0。但是,这也无法掩盖这个函数对启动线程数量的帮助。

#include <thread>
#include <numeric>
#include <algorithm>
#include <functional>
#include <vector>
#include <iostream>
using namespace std;

template<typename Iterator,typename T>
struct accumulate_block
{
    void operator()(Iterator first,Iterator last,T& result)
    {
        result=std::accumulate(first,last,result);
    }
};

template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init)
{
    unsigned long const length=std::distance(first,last);   //该函数会返回[first, last)范围内包含的元素的个数

    if(!length)
        return init;

    unsigned long const min_per_thread=25;  //定义每个线程计算的任务量
    unsigned long const max_threads=        //通过每个线程计算的任务量和总任务计算所需线程数量,-1的原因是保证最少需要一个任务才能启动一个线程
        (length+min_per_thread-1)/min_per_thread;
    cout<<"max thread:"<<max_threads<<endl;
    unsigned long const hardware_threads=
        std::thread::hardware_concurrency();     //返回能同时并发在一个程序中的线程数量,例如,多核系统中,返回值可以是CPU核芯的数量。

    cout<<"hardware_threads:"<<hardware_threads<<endl;
    unsigned long const num_threads=
        std::min(hardware_threads!=0?hardware_threads:2,max_threads);
    cout<<"num_threads:"<<num_threads<<endl;
    unsigned long const block_size=length/num_threads;

    std::vector<T> results(num_threads);
    std::vector<std::thread>  threads(num_threads-1);   //线程数量减一的原因是当前线程也参与计算,

    Iterator block_start=first;
    for(unsigned long i=0;i<(num_threads-1);++i)
    {
        Iterator block_end=block_start;
        //将迭代器往后移动 block_size 大小
        std::advance(block_end,block_size);
        threads[i]=std::thread(
            accumulate_block<Iterator,T>(),
            block_start,block_end,std::ref(results[i]));
        block_start=block_end;
    }
    //当前线程也参与计算部分任务量
    accumulate_block<Iterator,T>()(block_start,last,results[num_threads-1]);
    
    std::for_each(threads.begin(),threads.end(),
        std::mem_fn(&std::thread::join));

    return std::accumulate(results.begin(),results.end(),init);
}

int main()
{
    std::vector<int> vi;
    for(int i=0;i<100;++i)
    {
        vi.push_back(10);
    }
    int sum=parallel_accumulate(vi.begin(),vi.end(),5);
    std::cout<<"sum="<<sum<<std::endl;
}
[root@zs3-2 lesson1]# ./main
max thread:4
hardware_threads:32
num_threads:4
sum=1005

这个demo有点苦涩难懂,多花点时间,结合2.4量产线程,等待线程结束一起思考更好。

2.5 识别线程

线程标识类型是std::thread::id,可以通过两种方式进行检索。第一种,可以通过调用std::thread对象的成员函数get_id()来直接获取,注意一定要先有std::thread对象哦。第二种,当前线程中调用std::this_thread::get_id()(这个函数定义在<thread>头文件中)也可以获得线程标识。

#include<iostream>
#include<thread>
using namespace std;
void thread_fun(){
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}

int main(){
    thread thr(thread_fun);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    cout<<"thr id:"<<thr.get_id()<<endl;
    thr.join();
    cout<<"thr id:"<<thr.get_id()<<endl;    //返回std::thread::type默认构造值,这个值表示“没有线程”
    cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    return 0;
}

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

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

相关文章

G1D15-fraud-APT-汇报-基础模型与LR相关内容总结-KG-cs224w colab1-ctf rce41-44

一、fraud 跑了一个lr模型&#xff0c;从正则&#xff0c;一直看到了极大似然和最大后验估计emmm。一路跑偏&#xff0c;已经0954了。先把实验结果抄一抄 本来想把模型都跑完&#xff0c;没想到看R补充了大量的基本知识&#xff08;L1\L2正则、先验概率 今天先来看fraud 看的…

Hive——详细总结Hive中各大查询语法

✅作者简介&#xff1a;最近接触到大数据方向的程序员&#xff0c;刚入行的小白一枚 &#x1f34a;作者博客主页&#xff1a;皮皮皮皮皮皮皮卡乒的博客 &#x1f34b;当前专栏&#xff1a;Hive学习进阶之旅 &#x1f352;研究方向&#xff1a;大数据方向&#xff0c;数据汇聚&a…

单隐层神经网络在Matlab上实现及其简单应用

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

Kafka 认证三:添加 Kerberos 认证详细流程

背景 上一章节介绍了 Kerberos 服务端和客户端的部署过程&#xff0c;本章节继续介绍 Kafka 添加 Kerberos 认证的部署流程&#xff0c;及 Java API 操作的注意事项。 sasl.kerberos.service.name 配置的含义 Kafka 添加 Kerberos 部署的核心是 Kafka 服务端的 Principal 配…

基于gensim实现word2vec模型(附案例实战)

目录 什么是word2vec&#xff1f; Word2Vec的原理 gensim实现word2vec模型&#xff08;实战&#xff09; 什么是word2vec&#xff1f; Word2Vec是google在2013年推出的一个NLP工具&#xff0c;它的特点是能够将单词转化为向量来表示&#xff0c;这样词与词之间就可以定量的…

20+个很棒的 Python 脚本的集合(迷你项目)

&#x1f482; 个人网站:【海拥】【摸鱼小游戏】【神级源码资源网站】&#x1f91f; 风趣幽默的前端学习课程&#xff1a;&#x1f449;28个案例趣学前端&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】&#x1f4ac; 免费且实用的 前…

【软件分析第17讲-学习笔记】程序综合 Program Synthesis

文章目录前言正文程序综合枚举法CEGIS&#xff1a;基于反例的优化约束求解法启发式搜索法统计法基于组件的程序综合 Component-Based Synthesis小结参考文献前言 创作开始时间&#xff1a; 如题&#xff0c;学习一下程序综合 Program Synthesis的相关知识。参考&#xff1a;熊…

AUTOSAR-Fee模块

(73条消息) AUTOSAR-Fee模块_一ye残雪的博客-CSDN博客_fee 配置 0 前言 Fee模块全称Flash EEPROM Emulation Module&#xff0c;属于ECU抽象层 Fee模块本身是脱离硬件的&#xff0c;但是Fee模块可能会引用的Fls模块定制API&#xff0c;所以只能算半抽象 本文中&#xff0c;由于…

数据库高级 III

数据库高级 III 二叉排序树在极端情况下存在的问题 二叉排序树在极端情况下会产生失衡二叉树 失衡二叉树其实是不希望存在的&#xff0c;因为它失去了二叉排序树的查询优势&#xff0c;现在这种失衡二叉树的查询效率和单向链表一样&#xff0c;此时它就是单向链表 数据结构…

14.4、SpringWebFlux-1

14.4、SpringWebFlux-1 14.4.1、前置知识 SpringMVC&#xff0c;SpringBoot&#xff0c;Maven&#xff0c;Java8 新特性 14.4.2、基本介绍 官方文档 Web on Reactive Stack (spring.io) 是 Spring5 添加新的模块&#xff0c;用于 web 开发的&#xff0c;功能 SpringMVC 类…

网络热传App鉴定 |「得物」疑私删用户视频?从技术角度还原事件始末

声明&#xff1a;本文更注重于原理知识的普及&#xff0c;因此文中不会有大量实际代码的展示&#xff0c;如果想从代码层面上了解「应用存储分区」的内容&#xff0c;欢迎阅读我两年前写过的技术文章《Android 10 应用分区存储适配实践》 近日&#xff0c;有网友爆料&#xff0…

MySQL 分库分表

MySQL分库分表 概念 读写分离优化了互联网读多写少场景下的性能问题&#xff0c;考虑一个业务场景&#xff0c;如果读库的数据规模非常大&#xff0c;除了增加多个从库之外&#xff0c;还有其他的手段吗&#xff1f;实现数据库高可用&#xff0c;还有另外一个撒手锏&#xff…

Python性能优化指南--让你的Python代码快x3倍的秘诀

Python性能优化指南 Python最为人诟病的就是其执行速度。如何让Python程序跑得更快一直是Python核心团队和社区努力的方向。作为Python开发者&#xff0c;我们同样可以采用某些原则和技巧&#xff0c;写出性能更好的Python代码。本文将带大家深入探讨Python程序性能优化方法。…

99页4万字XX大数据湖项目建设方案

目 录 1. 项目综述 1.1. 项目背景 1.2. 项目目标 1.3. 项目建设路线 2 需求分析 2.1功能需求 2.1.1 统一数据接入 2.1.2 数据迁移 2.1.3 数据范围与ETL 2.1.4 报表平台 2.1.5 安全管理 2.1.6 数据治理 2.2非功能需求 2.2.1运维保障需求 2.2.2可用性需求 2.2.3可…

MQTT 具备那些特征?

目录 1、MQTT 中的 QoS&#xff08;消息服务质量&#xff09; &#xff08;1&#xff09;为什么服务质量&#xff08;QoS&#xff09;很重要? &#xff08;2&#xff09;QoS 在 MQTT 中是如何工作的? &#xff08;3&#xff09;如何选择正确的 QoS 级别 &#xff08;4&a…

Java开发中Word转PDF文件5种方案横向评测

Java开发中Word转PDF文件5种方案横向评测 前段时间接了个项目&#xff0c;需要各种处理Word模板、转PDF、签章等等&#xff0c;非常头疼&#xff0c;其中光是一个word转PDF就折磨我好久&#xff0c;实现转换很简单&#xff0c;但是效果总是达不到满意&#xff0c;于是我把市面…

【Linux】关于普通用户无法使用sudo指令的解决方案

文章目录前言解决方案结语前言 在这篇博客中&#xff0c;测试 rm -rf 删除文件时无视权限暴力删除的效果时&#xff0c;使用了 sudo 指令。 但是sudo指令是不能直接使用的&#xff0c;需要修改一些设置。 当时我遇到这个问题时&#xff0c;困惑了许久&#xff0c;查找解决方…

JVM执行引擎

文章目录学习资料执行引擎概述工作过程Java代码编译和执行的过程什么是解释器&#xff08;Interpreter&#xff09;&#xff0c;什么是JIT编译器&#xff1f;为什么说Java是半编译半解释型语言&#xff1f;机器码、指令、汇编语言、高级语言机器码指令指令集汇编语言高级语言字…

UE5实现PS图层样式投影效果

一、PS图层样式投影效果 1、创建材质函数 MF_PS_Style_Shadow 公开到库&#xff08;可选&#xff09; 定义 function input。 Shadow代码&#xff1a; /** PS图层样式投影效果param {UVs} texture coordinateparam {TextureObject} texture objectparam {TextureSize} …

十、children的深入用法-React.Children对象上的方法

目标 理解什么是children掌握React.Children对象上的方法 知识点 什么是children上图中我们看到了&#xff0c;我们之前学过的React.createElement方法&#xff0c;现在大家发现jsx的内容&#xff0c;全部都体现在了该方法上&#xff1b;那么React.createElement其实是有三个…