C++线程std::thread创建 thread封装线程操作,通过std::tread(func)
传入函数指针,创建一个对象并初始化即开启一个线程执行
注意:
这里可以传入成员函数,但必须加&
这里也可以传入仿函数,但是编译器会认为t1是一个函数指针的类型,可以使用{}统一初始化来指出这是一个thread对象
1 2 3 4 5 6 7 8 9 10 11 12 13 class func {public : void operator () () { std::cout << "这是一个仿函数" << std::endl; } }; int main () { std::thread t1 (func()) ; std::thread t2{func ()}; t2. join (); std::cout << typeid (t1).name () << std::endl; std::cout << typeid (t2).name () << std::endl; }
class std::thread __cdecl(class func (__cdecl*)(void)) class std::thread
创建线程必须对应线程对象join()或detach()调用,否则报错,这是因为thread对象的析构时直接调用_STD terminate()来终止程序
1 2 3 4 5 ~thread () noexcept { if (joinable ()) { _STD terminate () ; } }
线程分离
detach线程分离,在MSVC里随着主线程结束意味整个进程结束,这个分离的子线程也会被强制回收,这就是为什么上面结果看不到”这是一个仿函数”这一行
使用detach要注意局部变量在主线程有无被释放,在不改变代码逻辑情况下可以使用拷贝复制 或者智能指针
thread源码: 当调用thread构造函数时候就开始调用_Start启动线程
1 2 3 4 template <class _Fn , class ... _Args, enable_if_t <!is_same_v<_Remove_cvref_t<_Fn>, thread>, int > = 0 >_NODISCARD_CTOR explicit thread (_Fn&& _Fx, _Args&&... _Ax) { _Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); }
_Start里面命名了 _Tuple类型保存了函数和参数,函数结尾通过_Decay_copied.release()来释放所有权,若线程无效(参数为空),则会触发系统报错: std::system_error
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 template <class _Fn , class ... _Args>void _Start(_Fn&& _Fx, _Args&&... _Ax) { using _Tuple = tuple<decay_t <_Fn>, decay_t <_Args>...>; auto _Decay_copied = _STD make_unique <_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof ...(_Args)>{}); #pragma warning (push) #pragma warning (disable : 5039) _Thr._Hnd = reinterpret_cast <void *>(_CSTD _beginthreadex(nullptr , 0 , _Invoker_proc, _Decay_copied.get (), 0 , &_Thr._Id)); #pragma warning (pop) if (_Thr._Hnd) { (void ) _Decay_copied.release (); } else { _Thr._Id = 0 ; _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN); } }
_beginthreadex创建线程
nullptr
和 0
参数代表默认的线程安全属性和堆栈大小。
_Invoker_proc
是线程的入口函数指针。
_Decay_copied.get()
代表传递给线程的参数。
0
表示线程立即运行。
&_Thr._Id
是线程标识符的存储地址(保存新线程的 ID)。
入口函数指针怎么查找,_Get_invoke
函数直接返回_Invoke
函数调用的结果
1 2 3 4 template <class _Tuple , size_t ... _Indices>_NODISCARD static constexpr auto _Get_invoke(index_sequence<_Indices...>) noexcept { return &_Invoke<_Tuple, _Indices...>; }
get<_Indices>( _Tup )模板全特化将参数传给invoke函数,Indices是参数在tutple的下标
1 2 3 4 5 6 7 8 template <class _Tuple , size_t ... _Indices>static unsigned int __stdcall _Invoke(void * _RawVals) noexcept { const unique_ptr<_Tuple> _FnVals(static_cast <_Tuple*>(_RawVals)); _Tuple& _Tup = *_FnVals; _STD invoke (_STD move(_STD get<_Indices>(_Tup))...) ; _Cnd_do_broadcast_at_thread_exit(); }
在invoke,Obj是调用对象,Arg是参数,这里有一个c++14的新特性
auto -> decltype(...)
通过decltype尾随返回类型推导,c++14可以直接auto不需要添加decltype
检查_Call操作是否抛异常,直接执行(Obj)()然后返回执行结果
_Functor :普通函数或函数对象,直接调用 _Obj
。
_Pmf_object :指向对象的成员函数指针,调用格式为 (object.*function)(args...)
。
_Pmf_refwrap :指向对象成员函数的 std::reference_wrapper
,调用格式为 (refwrap.get().*function)(args...)
。
_Pmf_pointer :指向对象成员函数的指针,调用格式为 ((*pointer).*function)(args...)
。
_Pmd_object :指向数据成员的指针,调用格式为 object.*member
。
_Pmd_refwrap :指向数据成员的 std::reference_wrapper
,调用格式为 refwrap.get().*member
。
_Pmd_pointer :指向数据成员的指针,调用格式为 (*pointer).*member
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 template <class _Callable , class _Ty1 , class ... _Types2>_CONSTEXPR17 auto invoke (_Callable&& _Obj, _Ty1&& _Arg1, _Types2&&... _Args2) noexcept (...) -> decltype (...) { if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Functor) { return static_cast <_Callable&&>(_Obj)(static_cast <_Ty1&&>(_Arg1), static_cast <_Types2&&>(_Args2)...); } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_object) { return (static_cast <_Ty1&&>(_Arg1).*_Obj)(static_cast <_Types2&&>(_Args2)...); } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_refwrap) { return (_Arg1. get ().*_Obj)(static_cast <_Types2&&>(_Args2)...); } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_pointer) { return ((*static_cast <_Ty1&&>(_Arg1)).*_Obj)(static_cast <_Types2&&>(_Args2)...); } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_object) { return static_cast <_Ty1&&>(_Arg1).*_Obj; } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_refwrap) { return _Arg1. get ().*_Obj; } else { static_assert (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_pointer, "bug in invoke" ); return (*static_cast <_Ty1&&>(_Arg1)).*_Obj; } }
当回调函数参数为引用时,必须将参数显示转化为引用对象传递给线程的构造函数,否则无法通过编译
这是因为这里参数已经转换成右值了,而我们的回调函数参数是int&左值引用,不能绑定右值
解决方法:使用std::ref,将类型和地址包装在一个类对象中,可以看到源码根据_Pmd_refwrap
来跳转
1 (static_cast <_Ty1&&>(_Arg1), static_cast <_Types2&&>(_Args2)...)
线程管控
thread函数没有实现拷贝构造,只能移动,因此在使用容器的时候需要注意,vector容器的emplace可以直接传递参数进行构造
并行计算:std::thread::hardware_concurrency()获取cpu核心数
识别当前线程的标识,进程内唯一:std::this_thread::get_id()