当前位置: 首页 > news >正文

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();
http://www.jsqmd.com/news/342802/

相关文章:

  • 2026医院子母钟系统供应商选哪家?五大品牌综合评估与推荐 - 深度智识库
  • 基于深度学习的玉米虫害检测系统演示与介绍(YOLOv12/v11/v8/v5模型+Pyqt5界面+训练代码+数据集)
  • bazel报错:@com_google_absl//absl/container: Unable to load file @rules_cc//cc:cc_library.bzl
  • 2026学校标准化考场电子时钟五大厂家对比分析首选推荐指南 - 深度智识库
  • 实用指南:django rest framework:从零开始搭建RESTful API
  • 2026医院子母钟系统供应商推荐:西安伟洲电子科技引领精准时间同步新标准 - 深度智识库
  • 6.8 Bookinfo故障排查实战:服务调用失败、性能瓶颈诊断技巧
  • 【金融项目实战】3_接口测试 _提取测试点和编写用例
  • 设计副业技能匹配工具,输入自身技能,匹配需求副业,标注技能提升方向,帮助从业者发挥优势,提升副业竞争力。
  • 制作小商家营销方案生成工具,输入店铺类型及目标人群,生成适配营销方案(线上/线下),标注执行步骤,帮小商家低成本获客。
  • [信息论与编码理论专题-18]:信息熵 = 一件事的“不可预测程度”,并且用数学度量
  • 【ACM模式】队列操作
  • 2026年北斗NTP网络时间服务器厂家TOP5推荐:精准授时助力行业数字化升级 - 深度智识库
  • 我花了一天时间,拆了一下 OpenTeleDB 的 XStore,到底解决了 PG 的哪根老筋?
  • AI代理:AI原生应用领域的关键驱动力
  • 使用darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74训练图片是怎么生成权重文件的,怎么定义权重文件名?
  • 26年人形机器人谁领跑 智平方依托GOVLA大模型+近5亿订单跻身十强
  • AI产品经理核心能力图谱:不只是写Prompt,这些能力才是关键!
  • Plotly + Dash:构建交互式数据仪表盘的艺术与实战
  • 进程与线程:8核CPU究竟能创建多少?
  • 实测中石化加油卡回收平台,京顺回收闲置卡券变现优选 - 京顺回收
  • Kmesh-Waypoint 深度解析:Kmesh 服务网格的七层流量管理引擎
  • 模型服务化这件事:从 Batch 到 Stream,不只是改个部署方式那么简单
  • 提示工程架构师工具选型:破解Agentic AI技术挑战的6款必备开源框架
  • AI大模型产品经理修炼手册 | 七阶段学习路线,收藏不迷路
  • Itinerary(行程单)
  • LLM支持的AI Agent实体链接技术
  • 从不会AI到转型产品经理:一位35+研发的100天真实记录
  • 人机共创在AI原生应用中的发展路径探索
  • 10年产品总监揭秘:AI产品经理必备的6大核心能力与转型指南