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

std::thread详解

std::thread

thread是个类,它的构造函数是个函数模板,且使用了万能引用,thread维护着一个tuple成员,在构造函数中将推导出的类型用remove_reference去掉引用然后传给tuple这个类模板,这样tuple中保存的就是值类型而没有引用类型,再用推导出的类型给引用变量进行完美转发,然后传给tuple的构造函数,这样tuple中的值就是根据给thread传的实参来进行拷贝构造或移动构造,thread设计一个static成员函数,这个函数是void*(*)(void*)类型,这个成员函数利用void*参数得到tuple,然后auto func=get<0>(move(tuple)) ,tuple以右值传给get,所以tuple中第一个值也就是函数是以右值返回的,auto只有万能引用会推导引用类型,其他都是值,所以移动构造得到func,接着遍历tuple剩下的参数,全部都是给get传参move后的tuple,使tuple中参数都以右值的形式返回,然后传给可调用对象func。thread在构造函数函数体中调用pthread_create,参数传tuple的地址,函数的话传那个成员函数,然后创建新线程,执行成员函数,成员函数利用void*参数得到tuple,从tuple中以右值的形式取出可调用对象,然后再从tuple中以右值的形式把参数再都取出来然后传给可调用对象

关键点:

1、tuple中保存的是值类型,tuple中的元素在构造时使用完美转发

2、tuple中的参数是以右值的形式传给可调用对象的

如何传主线程的值给新线程呢?


#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std;void func(int& t)
{cout<<t;
}int main()
{int a=10;thread th(func,a);th.join();return 0;
}

来详细分析一把上面代码,func和a都是左值,所以thread构造函数万能引用,推导的都是左值引用类型,然后去掉引用作为tuple的类型模板参数,tuple中的元素都以值形式保存,调用拷贝构造来初始化,后面thread构造函数调用pthread_create,执行thread的成员函数,从void*解析出tuple结构体,然后用get,以右值的形式获取了可调用对象,用auto来接受,上演了一出移动构造的戏码,然后以右值获取其他的tuple中的参数然后传给可调用对象,问题来了,tuple中的int变量以右值的形式返回,但是可调用对象是用左值引用变量来接收,所以会报错

#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std;
void Print(int n, int& rx, mutex& rmtx)
{rmtx.lock();for (int i = 0; i < n; i++){// t1 t2++rx;}rmtx.unlock();
}int main()
{int x = 0;
mutex mtx;// 这⾥必须要⽤ref()传参,现成中拿到的才是x和mtx的引⽤thread t1(Print, 1000000, ref(x), ref(mtx));
thread t2(Print, 2000000, ref(x), ref(mtx));
t1.join();
t2.join();
cout << x << endl;return 0;
}
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr,void *(*start_rtn)(void *), void *restrict arg);

如何将主线程的值传给可调用对象呢?第一种策略就是从参数下手,使用ref模板函数,传参x,会创建reference_wrapper,其底层维护一个指针int*,用x去初始化这个指针,然后值返回的形式返回reference_wrapper,tupple中会有reference_wrapper,并以移动构造来初始化,因为值返回会创建临时变量,就是右值,后面使用get函数从tuple中以右值形式返回reference_wrapper,左值引用变量接收,自动执行operator type&成员函数,解引用维护的指针并以左值形式返回对象,然后左值引用变量就可以接受了,获取地址

void Print(int n, int& rx, mutex& rmtx)
{rmtx.lock();for (int i = 0; i < n; i++){// t1 t2++rx;}rmtx.unlock();
}int main()
{int x = 0;
mutex mtx;// 将上⾯的代码改成使⽤lambda捕获外层的对象,也就可以不⽤传参数,间接解决了上⾯的问
题auto Print = [&x, &mtx](size_t n) {mtx.lock();for (size_t i = 0; i < n; i++){++x;}mtx.unlock();
};thread t1(Print, 1000000);
thread t2(Print, 2000000);
t1.join();
t2.join();
cout << x << endl;return 0;
}

将主线程的变量传给新线程的第二种方法就是从函数下手,直接让处理函数绑定一些参数,或者是像lambda表达式这样捕获一些变量,直接省了很多逻辑,更好理解了

remove_reference、remove_const

1、remove_reference

在thread的原理中涉及了类型的去引用化,就可以使用remove_reference来实现

template <class T>
struct remove_reference {using type = T;  // 默认情况:T 不是引用,直接保留原类型
};template <class T>
struct remove_reference<T&> {  // 特化处理左值引用using type = T;
};template <class T>
struct remove_reference<T&&> {  // 特化处理右值引用using type = T;
};

remove_reference是一个模板类,对于模板类的类型模板参数要么显式给、要么缺省给,还有模板特化,以前一直不太清楚模板特化的作用,现在来看模板特化就是特定的类型模板参数,使其特化出特别的类。像remove_reference如果显式给一个普通类型,那就用using给这个普通类型取一个叫type的类型别名,显式给的是左值引用类型,那type就是去了引用后的类型的别名,类型模板参数传右值引用类型同理

#include <type_traits>
#include <iostream>int main() {// 使用 remove_reference::typestd::remove_reference<int>::type a = 10;          // intstd::remove_reference<int&>::type b = 20;         // intstd::remove_reference<int&&>::type c = 30;        // intstd::cout << a << ", " << b << ", " << c << std::endl;
}

2、remove_const

template< class T > 
struct remove_const {typedef T type;  // 主模板:直接保留原类型
};template< class T > 
struct remove_const<const T> {typedef T type;  // 特化版本:移除 const 限定符
}

1、模板特化,对于特定的类型模板参数特化出特定的类

2、使用typedef或using给类型取别名,保存在实例化出来的类中

std::tuple和std::get

1、std::tuple

tuple是一个类模板

template <class... Types> class tuple;

需要显式给类型模板参数,然后tuple会根据类型模板参数来创建有名变量,接着就是调用构造函数来初始化这些元素

template <class... UTypes>explicit tuple (UTypes&&... elems);

构造函数是一个函数模板,应用了万能引用,注意不要把构造函数的函数模板和类模板合一块了,这俩各是各的类型模板参数。

构造函数会用万能引用加完美转发美美地将元素都初始化了

2、std::get

(1)
template <size_t I, class... Types>
typename tuple_element< I, tuple<Types...> >::type& get(tuple<Types...>& tpl) noexcept;
(2)
template <size_t I, class... Types>
typename tuple_element< I, tuple<Types...> >::type&& get(tuple<Types...>&& tpl) noexcept;

get是函数模板,因为这边非类型模板参数没有设置缺省值,所以需要显式给,其他的类型模板参数都可以由参数的类型推导给得到,这边参数就是tuple,有两种传参方式,一种左值一种右值,这会影响到返回tuple的元素时是以左值形式返回还是右值形式返回,至于非类型模板参数就是tuple第几个元素

注意:get是函数模板,且没使用万能引用,因为不符合格式T&&

std::ref和std::refenrence_wrapper

1、std::ref

ref是函数模板,可以使用左值或const左值来传参,然后ref函数会用推导出的类型模板参数T来构造reference_wrapper对象,其底层维护着一个T*类型的指针,用ref引用变量形参来初始化这个指针。如果传的是左值,那T推导的就是左值,T传给reference_wrapper,于是其底层维护的指针就是左值指针,传的是const左值,那T就是const左值类型,T传给reference_wrapper,其底层维护的指针就是const指针,指向的对象不可变。这个函数会返回一个reference_wrapper对象,值返回的形式,所以返回的是个右值

template <class T> reference_wrapper<T> ref (T& elem) noexcept;
template <class T> void ref (const T&&) = delete;

2、std::reference_wrapper

template <class T> class reference_wrapper;

这是一个类模板,底层维护着一个T*的指针

reference_wrapper (type& ref) noexcept;
reference_wrapper (type&&) = delete;

这是其构造函数,利用左值来初始化指针变量成员,可以看到右值的构造直接被禁了,没错,引用包装器只包装左值

reference_wrapper有operator type& 成员函数,会在reference_wrapper强转type&类型时调用,将维护的变量以type&也就是左值形式返回,这也是为什么get从tuple中以右值的形式得到reference_wrapper后,最后自定义可调用对象的引用变量可以成功接收到主线程的变量的原因

提问:为何reference_wrapper不用引用变量来维护左值或const左值呢?

如果reference_wrapper底层是引用变量,引用变量在初始化绑定对象后底层维护的指针就没法改了,所以直接用指针,这样就可以实现reference_wrapper的赋值运算符重载

http://www.dtcms.com/a/360590.html

相关文章:

  • Spring Boot单体项目整合Nacos
  • C++17 折叠表达式(Fold Expressions)详解
  • ConcurrentHashMap在扩容的过程中又有新的数据写入是怎么处理的
  • 《Bishop PRML》10.1 (3) 理解VAE reconstruction loss
  • Redis 中的 Bitmap 与 Bitfield 及 Java 操作实践
  • python如何下载svg图片
  • 【Proteus仿真】数码管控制系列仿真——单个数码管控制/多数码管控制
  • leetcode 260 只出现一次的数字III
  • 你的数据是如何被保护的?
  • Linux系统的进程管理
  • vue3+vite+ts 发布npm 组件包
  • 查看所有装在c盘软件的方法
  • [知识点记录]SQLite 数据库和MySQL 数据库有什么区别?
  • DuckDB 内嵌分析:ABP 的「本地 OL盘快照」
  • 福彩双色球第2025100期号码推荐
  • 福彩双色球第2025100期数据统计
  • 吴恩达机器学习作业十一:异常检测
  • Docker 容器(二)
  • 机器视觉学习-day15-图像轮廓特征查找
  • Wi-Fi技术——OSI模型
  • 深度学习量化双雄:PTQ 与 QAT 的技术剖析与实战
  • 开源协作白板 – 轻量级多用户实时协作白板系统 – 支持多用户绘图、文字编辑、图片处理
  • globals() 小技巧
  • C++ 模板全览:从“非特化”到“全特化 / 偏特化”的完整原理与区别
  • Prometheus之启用--web.enable-remote-write-receiver
  • 基于muduo库的图床云共享存储项目(三)
  • 前端常见安全问题 + 防御方法 + 面试回答
  • 「数据获取」《中国工会统计年鉴》(1991-2013)(获取方式看绑定的资源)
  • 【人工智能99问】Qwen3简介(33/99)
  • 浅析NVMe协议:DIF