news 2026/6/10 4:43:22

C++并发编程学习(二)—— 线程所有权和管控

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++并发编程学习(二)—— 线程所有权和管控

文章目录

    • 一、线程归属权
      • 移交线程归属权
      • 线程容器存储
    • 二、在运行时选择线程数量
    • 三、识别线程

一、线程归属权

移交线程归属权

std::thread支持移动语义,可以实现函数创建线程并将归属权移交给函数调用者,和创建线程并将其归属权传入某个函数的功能。
对于std::thread C++ 不允许其执行拷贝构造和拷贝赋值, 所以只能通过移动和局部变量返回的方式将线程变量管理的线程转移给其他变量管理。

#include <thread> #include <chrono> #include <iostream> void some_function() { while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); } } void some_other_function() { while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { //t1 绑定some_function std::thread t1(some_function); //2 转移t1管理的线程给t2,转移后t1无效 std::thread t2 = std::move(t1); std::cout << "moved t1 to t2" << "\n"; //3 t1 可继续绑定其他线程,执行some_other_function t1 = std::thread(some_other_function); std::cout << "construction t1 again" << "\n"; //4 创建一个线程变量t3 std::thread t3; //5 转移t2管理的线程给t3 t3 = std::move(t2); std::cout << "moved t2 to t3" << "\n"; //6 转移t3管理的线程给t1 t1 = std::move(t3); std::cout << "moved t3 to t1" << "\n"; std::this_thread::sleep_for(std::chrono::seconds(2000)); }

上述代码打印信息:

moved t1 to t2 construction t1 again moved t2 to t3 terminate called without an active exception

上面代码将t2管理的线程交给t3
之后将t3管理的线程交给t1,此时t1管理线程运行着 some_function,
步骤6导致崩溃的原因就是将t3管理的线程交给t1,而此时t1正在管理线程运行some_other_function。
所以我们可以得出一个结论,就是不要将一个线程的管理权交给一个已经绑定线程的变量,否则会触发线程的terminate函数引发崩溃。赋值操作也有类似的原则:只要std::thread对象正管控着一个线程,就不能简单地向它赋新值,否则该线程会因此被遗弃。

std::thread支持移动操作的意义是,函数可以便捷地向外部转移线程的归属权:
从函数内部返回std::thread对象:

std::threadf(){voidsome_function();returnstd::thread(some_function);}std::threadg(){voidsome_other_function(int);std::threadt(some_other_function,42);returnt;}

类似地,若归属权可以转移到函数内部,函数就能够接收std::thread实例作为按右值传递的参数:

voidf(std::thread t);voidg(){voidsome_function();f(std::thread(some_function));std::threadt(some_function);f(std::move(t));}

线程容器存储

容器存储线程时,比如vector,如果用push_back操作势必会调用std::thread,这样会引发编译错误,因为std::thread没有拷贝构造函数。我们可以使用emplace_back,避免调用thread的拷贝构造函数。

voiddo_work(unsignedid);voidf(){std::vector<std::thread>threads;for(unsignedi=0;i<20;++i){threads.emplace_back(do_work,i);// 生成线程}for(auto&entry:threads)// 依次在各线程上调用join()函数entry.join();}

二、在运行时选择线程数量

用C++标准库的std::thread::hardware_concurrency()函数,它的返回值是一个指标,表示程序在各次运行中可真正并发的线程数量。下面代码是并行版的std::accumulate()的简单实现

#include<thread>#include<vector>#include<iostream>#include<numeric>template<typenameIterator,typenameT>structaccumulate_block{voidoperator()(Iterator first,Iterator last,T&result){result=std::accumulate(first,last,result);}};template<typenameIterator,typenameT>Tparallel_accumulate(Iterator first,Iterator last,T init){unsignedlongconstlength=std::distance(first,last);if(!length)returninit;unsignedlongconstmin_per_thread=25;unsignedlongconstmax_threads=(length+min_per_thread-1)/min_per_thread;// 真正的可并行线程数,等于CPU核数unsignedlongconsthardware_threads=std::thread::hardware_concurrency();unsignedlongconstnum_threads=std::min(hardware_threads!=0?hardware_threads:2,max_threads);std::cout<<"hardware threads num: "<<hardware_threads<<std::endl;unsignedlongconstblock_size=length/num_threads;// 各线程需分担的元素数量std::vector<T>results(num_threads);std::vector<std::thread>threads(num_threads-1);Iterator block_start=first;for(unsignedlongi=0;i<num_threads-1;++i){Iterator block_end=block_start;std::advance(block_end,block_size);// 将迭代器从当前位置向前移动 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]);// 发起全部线程后,主线程随之处理最后一个小块for(auto&entry:threads)entry.join();returnstd::accumulate(results.begin(),results.end(),init);}voiduse_parallel_acc(){std::vector<int>vec(1000000);for(inti=0;i<1000000;++i)vec.push_back(i);intsum=0;sum=parallel_accumulate<std::vector<int>::iterator,int>(vec.begin(),vec.end(),sum);std::cout<<"sum: "<<sum<<std::endl;}intmain(){use_parallel_acc();std::this_thread::sleep_for(std::chrono::seconds(2));return0;}

三、识别线程

程ID所属类型是std::thread::id,它有两种获取方法。首先,在与线程关联的std::thread对象上调用成员函数get_id(),即可得到该线程的ID。如果std::thread对象没有关联任何执行线程,调用get_id()则会返回一个std::thread::id对象,它按默认构造方式生成,表示“线程不存在”​。其次,当前线程的ID可以通过调用std::this_thread::get_id()获得,函数定义位于头文件<thread>

C++标准库容许我们随意判断两个线程ID是否相同,并且std::thread::id型别具备全套完整的比较运算符

std::thread::id master_thread;voidsome_core_part_of_algorithm(){if(std::this_thread::get_id()==master_thread){do_master_thread_work();}do_common_work();}

也可以输出线程ID:

std::cout<<std::this_thread::get_id();
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 11:57:48

内存破坏调试技巧

1、非修改序列算法 这些算法不会改变它们所操作的容器中的元素。 1.1 find 和 find_if find(begin, end, value)&#xff1a;查找第一个等于 value 的元素&#xff0c;返回迭代器&#xff08;未找到返回 end&#xff09;。find_if(begin, end, predicate)&#xff1a;查找第…

作者头像 李华
网站建设 2026/6/10 11:05:22

AI产品经理核心能力图谱:不只是写Prompt,这些能力才是关键!

文章解析了AI产品经理与传统产品经理的本质区别&#xff0c;阐述了三大职责&#xff08;需求定义、跨域协同、效果迭代&#xff09;和四大核心能力&#xff08;技术理解力、业务洞察力、数据敏感度、风险把控力&#xff09;。AI产品经理不是简单会写Prompt&#xff0c;而是需要…

作者头像 李华
网站建设 2026/6/10 11:28:45

物种分布曲线的五个矩

物种分布曲线的五个矩 引言 正文 第一个矩:均值(一阶矩) 第二个矩:方差 σ 2 \sigma^2 σ2 (二阶中心矩) 第三个矩:偏度(Skewness,三阶标准化矩) 第四个矩:峰度(Kurtosis,四阶标准化矩) 第五个矩:五阶矩(Hyperskewness / Tail asymmetry) 绘图代码 Author: JiJi …

作者头像 李华
网站建设 2026/6/10 11:30:09

【PMP】风险管理

一、风险基础核心概念 1. 风险本质与管理流程 定义&#xff1a;未来可能发生的、影响项目目标&#xff08;范围/进度/成本/质量&#xff09;的不确定事件&#xff08;含威胁和机会&#xff09;。核心流程&#xff08;必记&#xff09;&#xff1a;识别风险 → 定性风险分析 → …

作者头像 李华
网站建设 2026/6/10 15:10:29

法尔斯新闻社1398年波斯语新闻数据集_29万条_多领域分类_完整文本内容_自然语言处理_文本挖掘_机器学习训练数据

法尔斯新闻社1398年波斯语新闻数据集 引言与背景 法尔斯新闻社1398年波斯语新闻数据集是一个大规模、高质量的波斯语新闻文本数据集&#xff0c;收录了法尔斯新闻社在1398年&#xff08;即2019-2020年&#xff09;期间发布的全部新闻文章。该数据集包含294,023条新闻记录&#…

作者头像 李华