news 2026/9/2 3:40:21

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/9/2 19:57:53

提示工程架构师工具选型:破解Agentic AI技术挑战的6款必备开源框架

提示工程架构师工具选型&#xff1a;破解Agentic AI技术挑战的6款必备开源框架 一、引入与连接&#xff1a;当Agentic AI遇到“能力边界” 1. 一个真实的“Agent崩溃现场” 某电商平台的智能导购Agent上线3天就收到了127条投诉&#xff1a; 用户问“这件衣服洗了会缩水吗&a…

作者头像 李华
网站建设 2026/8/28 2:15:03

2026年大模型16项核心Benchmark全解析,选型不再迷茫

本文详解2026年大模型评估体系中的16项全维度核心Benchmark&#xff0c;涵盖通用常识、学术研究、编程实战等能力维度。通过指标详解、术语解码和案例拆解&#xff0c;帮助读者理解如何根据自身需求选择合适模型&#xff0c;并给出避坑指南&#xff0c;强调匹配度比平均分更重要…

作者头像 李华
网站建设 2026/9/2 20:40:33

2025年DT SCI1区TOP,无人机编队鲁棒多目标任务规划: 融合任务预分配与重分配框架,深度解析+性能实测

目录1.摘要2.无人机编队任务的预先分配与重新分配问题3.改进多目标进化算法4.结果展示5.参考文献6.代码获取7.算法辅导应用定制读者交流1.摘要 针对无人机编队在任务执行过程中因平台损毁或目标未完成而导致作战效能下降的问题&#xff0c;本文提出了一种融合任务预分配与重分…

作者头像 李华
网站建设 2026/9/2 20:39:11

米尔顿·弗里德曼《实证经济学方法论》解读

米尔顿弗里德曼《实证经济学方法论》解读 米尔顿弗里德曼的《实证经济学方法论》是1953年收录于其《实证经济学论文集》的开篇纲领性论文&#xff0c;是现代实证经济学发展的基石性文献。该文以工具主义哲学为内核&#xff0c;打破了传统经济学方法论的混乱局面&#xff0c;确…

作者头像 李华
网站建设 2026/9/2 20:08:00

数据立方体在智慧城市建设中的关键作用

数据立方体&#xff1a;打通智慧城市数据壁垒的核心引擎 摘要/引言&#xff1a;当智慧城市遇到“数据孤岛”难题 清晨7点&#xff0c;深圳南山区的上班族小李盯着手机上的导航APP——屏幕上红得发亮的拥堵路段像一条凝固的血管。与此同时&#xff0c;3公里外的环保监测站发出…

作者头像 李华