C++ thread线程函数传参原理剖析

您所在的位置:网站首页 thread函数详解 C++ thread线程函数传参原理剖析

C++ thread线程函数传参原理剖析

2023-03-21 19:26| 来源: 网络整理| 查看: 265

知识的学习在于点滴记录,坚持不懈;知识的学习要有深度和广度,不能只流于表面,坐井观天;知识要善于总结,不仅能够理解,更知道如何表达!

目录 thread线程函数参数问题学习下thread类的源码从VS2019的C++类库中看thread的源码invoke函数简化问题std::ref(data)为什么就可以了

thread线程函数参数问题

从C++11开始,终于提供了语言级别的thread类库,从此可以通过C++语言编写多线程程序,做到一次编写,到处编译。thread对象可以传递普通函数、函数对象、lambda表达式等作为线程函数,使用起来非常方便。最近看到有人(tony)提出这样一个问题,代码片段:

#include #include void handler1(int b) // 此处形参b,接收t1实参a的值,正确!!! { std::cout int a = 10; std::thread t1(handler1, a); t1.join(); // std::thread t2(handler2, a); 这里编译错误!!! // t2.join(); }

tony定义了一个thread对象,绑定了handler2线程函数,handler2的参数是一个普通的左值引用变量int &b,为什么不能接收实参a? 也就是std::thread t2(handler2, a); 这句代码直接编译错误,在visual studio 2019上(编译默认使用C++ 14标准)错误信息如下: 在这里插入图片描述 可以看到vs报错“invoke未找到匹配的重载函数”,具体原理后面给大家详细解释。如果线程函数非得用int &b这样的左值引用来接收,使用的时候如下即可:

#include #include void handler1(int b) { std::cout int a = 10; std::thread t1(handler1, a); t1.join(); std::thread t2(handler2, std::ref(a)); // 注意这里使用std::ref(a)即可!!! t2.join(); }

tony的问题就是,他写的线程函数handler2,形参是int &b,为什么不能直接接收实参变量a,但是用std::ref(a)又可以了,底层原理是什么?

学习下thread类的源码

要从原理上解释上面的问题,我们需要看看thread类的源码,主要看从定义一个thread对象,到线程函数的调用,中间都执行了哪些代码操作,通过查看源码,看看造成上面问题的根本原因是什么?

从VS2019的C++类库中看thread的源码

拷贝thread的源码,我们实现一个自己的线程类Slthread,如下:

#include #include using namespace std; class Slthread { public: template static unsigned int __stdcall _Invoke(void* _RawVals) noexcept /* terminates */ { // adapt invoke of user's callable object to _beginthreadex's thread procedure const unique_ptr _FnVals(static_cast(_RawVals)); _Tuple& _Tup = *_FnVals; _STD invoke(_STD move(_STD get(_Tup))...); return 0; } template _NODISCARD static constexpr auto _Get_invoke(index_sequence) noexcept { return &_Invoke; } template void _Start(_Fn&& _Fx, _Args&&... _Ax) { using _Tuple = tuple; auto _Decay_copied = _STD make_unique(_STD forward(_Fx), _STD forward(_Ax)...); constexpr auto _Invoker_proc = _Get_invoke(make_index_sequence{}); _Invoker_proc(_Decay_copied.get()); _Decay_copied.release(); } template explicit Slthread(_Fn&& _Fx, _Args&&... _Ax) { _Start(std::forward(_Fx), std::forward(_Ax)...); } }; void handler(int b) { std::cout std::cout std::cout std::cout return static_cast(_Obj)(static_cast(_Arg1), static_cast(_Args2)...); }

我们看到,实际上invoke函数的实现,就是调用第一个参数_Obj这个函数(handler函数),然后把后面的当作参数(实参a)传递给_Obj函数开始执行,所以啰嗦了半天,文章开始的问题,实际上就和下面的代码问题是等价的:

简化问题 #include using namespace std; template auto myinvoke(_Callable&& _Obj, _Ty1&& _Arg1, _Types2&&... _Args2) noexcept { return static_cast(_Obj)(static_cast(_Arg1), static_cast(_Args2)...); } void handler1(int b) { std::cout int a= 10; myinvoke(std::move(handler1), std::move(a)); // 编译正确,能够正常调用handler1函数 // myinvoke(std::move(handler2), std::move(a)); // 编译出错!!! }

现在一眼就可以看出来,为什么线程函数不能使用普通的左值引用int &b来接收实参a了,因为实参a传递的是右值,不能被一个左值引用变量int &b接收啊,类似:

int &b= std::move(a) // std::move(a)在上面invoke函数中,对应的就是static_cast(_Arg1)

很明显,上面的转换肯定是不行的。当然现在既然知道原理了,线程函数实参传递的是右值,那么形参用右值引用,或者常引用都可以,如下:

// void handler2(int &data) // 这个是错误的 void handler2(int &&data) // OK的 void handler2(const int &data) // OK的 std::ref(data)为什么就可以了

下面代码为什么就可以了?

#include using namespace std; template auto myinvoke(_Callable&& _Obj, _Ty1&& _Arg1, _Types2&&... _Args2) noexcept { return static_cast(_Obj)(static_cast(_Arg1), static_cast(_Args2)...); } void handler2(int &data) { std::cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3