news 2026/6/9 1:59:09

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/7 3:05:23

使用darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74训练图片是怎么生成权重文件的,怎么定义权重文件名?

&#x1f3c6;本文收录于 《全栈 Bug 调优&#xff08;实战版&#xff09;》 专栏。专栏聚焦真实项目中的各类疑难 Bug&#xff0c;从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解&#xff0c;形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者&…

作者头像 李华
网站建设 2026/6/5 15:16:11

人机共创在AI原生应用中的发展路径探索

人机共创在AI原生应用中的发展路径探索&#xff1a;从辅助到共生的三次进化 引言&#xff1a;当AI从“工具”变成“伙伴”——我们需要重新定义协作 你有没有过这样的经历&#xff1f; 用AI写文案时&#xff0c;它总抓不住你要的“感觉”——明明要的是“温暖的科技感”&…

作者头像 李华
网站建设 2026/6/8 0:47:45

从不会AI到转型产品经理:一位35+研发的100天真实记录

一位35在职研发面对AI转型焦虑&#xff0c;决定用100天记录从零学习AI并转型产品经理的真实过程。文章强调这不是成功案例包装&#xff0c;而是完整、不包装的转型实录&#xff0c;包括学习AI工具、产品实践、能力培养及每日真实记录。目标是帮助同样处境的普通人了解AI转型路径…

作者头像 李华
网站建设 2026/6/5 20:27:27

某教育企业AI创新孵化体系拆解:架构师眼中的3个核心价值

某教育企业AI创新孵化体系拆解&#xff1a;架构师眼中的3个核心价值 1. 引入与连接 1.1引人入胜的开场 在当今数字化浪潮汹涌澎湃的时代&#xff0c;教育领域正经历着前所未有的变革。想象一下&#xff0c;有一家教育企业&#xff0c;它不甘于传统教育模式的束缚&#xff0c;立…

作者头像 李华