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();
}
}

线程分离

  1. detach线程分离,在MSVC里随着主线程结束意味整个进程结束,这个分离的子线程也会被强制回收,这就是为什么上面结果看不到”这是一个仿函数”这一行

  2. 使用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

  • _Decay_copied是一个 _Tuple类型的智能指针,用来触发回调函数

  • _Invoker_proc是封装的可调用对象,这里应该通过模板特例化来跳转到不同的实现

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创建线程

  • nullptr0 参数代表默认的线程安全属性和堆栈大小。
  • _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 /* terminates */ {
// adapt invoke of user's callable object to _beginthreadex's thread procedure
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(); // TRANSITION, ABI
}

在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()