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 22 23
| template <class _Callable, class _Ty1, class... _Types2> _CONSTEXPR17 auto invoke(_Callable&& _Obj, _Ty1&& _Arg1, _Types2&&... _Args2) noexcept( noexcept(_Invoker1<_Callable, _Ty1>::_Call( static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...))) -> decltype(_Invoker1<_Callable, _Ty1>::_Call( static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...)) { 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; } }
|
_beginthreadex创建线程
nullptr
和 0
参数代表默认的线程安全属性和堆栈大小。
_Invoker_proc
是线程的入口函数指针。
_Decay_copied.get()
代表传递给线程的参数。
0
表示线程立即运行。
&_Thr._Id
是线程标识符的存储地址(保存新线程的 ID)。
入口函数指针怎么查找,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
1
| (static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...)
|
线程管控
joining_thread相当于重载赋值运算符,是得汇合后再移动
t.get_id()和std::this_thread::get_id()