0%

STL源码阅读(十二): std::thread源码分析

📌本文采用wolai制作,原文链接:https://www.wolai.com/ravenxrz/wQ6VWz7rhYAK4NWReW5ziB

std::thread 是c11后引入的标准线程库。作为c中最重要的线程封装库,有必要打开看看。

注:本文所有分析基于 gcc-8.5.0 源码

一些重要结论:

📌由此可以得到一个重要结论:给std::thread传入的参数是没有引用效果的。 如果要用引用,请使用std::ref

📌此处可知,thread::get_id 返回的是 pthread_t, 该值被pthread_create初始化,所以并不代表linux下的thread id

📌thread不支持copy语义,仅支持move语义, 且move operator=的前提是,当前线程已经不是joinable的,否则会有资源泄露。

1 构造函数·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename _Tp>
using __not_same = __not_<is_same<
typename remove_cv<typename remove_reference<_Tp>::type>::type, thread>>;

template <typename _Callable, typename... _Args,
typename = _Require<__not_same<_Callable>>>
explicit thread(_Callable &&__f, _Args &&...__args) {
static_assert(
__is_invocable<typename decay<_Callable>::type,
typename decay<_Args>::type...>::value,
"std::thread arguments must be invocable after conversion to rvalues");

#ifdef GTHR_ACTIVE_PROXY
// Create a reference to pthread_create, not just the gthr weak symbol.
auto __depend = reinterpret_cast<void (*)()>(&pthread_create);
#else
auto __depend = nullptr;
#endif
_M_start_thread(_S_make_state(__make_invoker(std::forward<_Callable>(__f),
std::forward<_Args>(__args)...)),
__depend);
}

逐行看:

  1. 构造函数是一个模板构造函数,模板生效条件是传入的_Callable模板类型不是thread类型。
  2. 最重要的是高亮的部分。

1.1 __make_invoker函数·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// A call wrapper that does INVOKE(forwarded tuple elements...)
template <typename _Tuple> struct _Invoker {
_Tuple _M_t;

template <size_t _Index>
static __tuple_element_t<_Index, _Tuple> &&_S_declval();

template <size_t... _Ind>
auto _M_invoke(_Index_tuple<_Ind...>) noexcept(
noexcept(std::__invoke(_S_declval<_Ind>()...)))
-> decltype(std::__invoke(_S_declval<_Ind>()...)) {
return std::__invoke(std::get<_Ind>(std::move(_M_t))...);
}

using _Indices =
typename _Build_index_tuple<tuple_size<_Tuple>::value>::__type;

auto operator()() noexcept( // 重点:最终线程的起点
noexcept(std::declval<_Invoker &>()._M_invoke(_Indices())))
-> decltype(std::declval<_Invoker &>()._M_invoke(_Indices())) {
return _M_invoke(_Indices());
}
};

template <typename... _Tp>
using __decayed_tuple = tuple<typename std::decay<_Tp>::type...>;

// Returns a call wrapper that stores
// tuple{DECAY_COPY(__callable), DECAY_COPY(__args)...}.
template <typename _Callable, typename... _Args>
static _Invoker<__decayed_tuple<_Callable, _Args...>>
__make_invoker(_Callable &&__callable, _Args &&...__args) {
return {__decayed_tuple<_Callable, _Args...>{
std::forward<_Callable>(__callable), std::forward<_Args>(__args)...}};
}

这里的invoker本质上是一个tuple,tuple的类型是对所有传入的类型的decay后的结果。

decay的作用:

  • 移除引用。
  • 移除 constvolatile 修饰符。
  • 将数组类型转换为指针类型。
  • 将函数类型转换为指向函数的指针类型。

decay的主要作用是让传入的类型按照值传递。

📌由此可以得到一个重要结论:给std::thread传入的参数是没有引用效果的。 如果要用引用,请使用std::ref

_Invoker类是thread最重要类,线程最终将通过pthread_create创建(下文分析),入口函数将转入这里的operator()

operator()重载,通过 std::__invoke调用tuple中存储的调用实体指针以及参数,这里用了move,所以如果有引用,实际上引用的是tuple中的变量。

1.2 _S_make_state函数·

__make_invoker的结果转入_S_make_state

1
2
3
4
5
template <typename _Callable> static _State_ptr _S_make_state(_Callable &&__f) {
using _Impl = _State_impl<_Callable>;
return _State_ptr{new _Impl{std::forward<_Callable>(__f)}};
}

这里包含两个类:

  1. _Impl
  2. _State_ptr

先看_Impl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Abstract base class for types that wrap arbitrary functors to be
// invoked in the new thread of execution.
struct _State {
virtual ~_State();
virtual void _M_run() = 0;
};

template <typename _Callable> struct _State_impl : public _State {
_Callable _M_func;

_State_impl(_Callable &&__f) : _M_func(std::forward<_Callable>(__f)) {}

void _M_run() { _M_func(); }
};

前文中的_Invoker存放在此处的_M_func

_State_ptr本质上就是一个unique_ptr:

std::unique_ptr源码分析见此。

1
2
using _State_ptr = unique_ptr<_State>;

所以_S_make_state即是一个对_invoker unique_ptr包装。

1.3 _M_start_thread函数·

_S_make_state的结果又转入到_M_state_thread

1
2
3
4
5
6
7
void thread::_M_start_thread(_State_ptr state, void (*)()) {
const int err = __gthread_create(&_M_id._M_thread,
&execute_native_thread_routine, state.get());
if (err)
__throw_system_error(err);
state.release();
}

看起来第二个参数没用,也即:

1
auto __depend = reinterpret_cast<void (*)()>(&pthread_create);

没用。

此处的_M_id为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
typedef pthread_t __gthread_t;

typedef __gthread_t native_handle_type;


// thread::id
class id {
native_handle_type _M_thread;

public:
id() noexcept : _M_thread() {}

explicit id(native_handle_type __id) : _M_thread(__id) {}

private:
friend class thread;
friend class hash<thread::id>;

friend bool operator==(thread::id __x, thread::id __y) noexcept;

friend bool operator<(thread::id __x, thread::id __y) noexcept;

// 这个就是打印的c++ thread id(注意不是linux上的tid)
template <class _CharT, class _Traits>
friend basic_ostream<_CharT, _Traits> &
operator<<(basic_ostream<_CharT, _Traits> &__out, thread::id __id);
};

id _M_id;

1.3.1 __gthread_create函数·

1
2
3
4
5
static inline int __gthread_create(__gthread_t *__threadid,
void *(*__func)(void *), void *__args) {
return __gthrw_(pthread_create)(__threadid, NULL, __func, __args);
}

到这里就很明显了,调用pthread_create创建线程. 线程的routine为:execute_native_thread_routine. 另外 thread_id也被pthread_create初始化。

📌此处可知,thread::get_id 返回的是 pthread_t, 该值被pthread_create初始化,所以并不代表linux下的thread id

1.3.2 execute_native_thread_routine·

1
2
3
4
5
6
static void *execute_native_thread_routine(void *__p) {
thread::_State_ptr __t{static_cast<thread::_State *>(__p)};
__t->_M_run();
return nullptr;
}

转为前面创建的invoker,并调用_M_run,最终进入如下函数:

1
2
3
4
5
6
// class _Invoker
auto operator()() noexcept( // 重点:最终线程的起点
noexcept(std::declval<_Invoker &>()._M_invoke(_Indices())))
-> decltype(std::declval<_Invoker &>()._M_invoke(_Indices())) {
return _M_invoke(_Indices());
}

这里前文已经提到:

_Invoker类是thread最重要类,线程最终将通过pthread_create创建(下文分析),入口函数将转入这里的operator()

2 joinable & join·

joinable源码:

1
2
bool joinable() const noexcept { return !(_M_id == id()); }

很简单,看id是否有被初始化。(即是否调用过pthread_create)

再看join

1
2
3
4
5
6
7
8
9
10
11
12
void thread::join() {
int __e = EINVAL;

if (_M_id != id())
__e = __gthread_join(_M_id._M_thread, 0);

if (__e)
__throw_system_error(__e);

_M_id = id(); // 标记成不可joinable的, 将对应析构函数处的检查
}

依然很简单, 如果thread是joinable的,则调用__gthread_join, 如果join成功,将_M_id置空。

析构函数:

1
2
3
4
5
6
~thread() {
if (joinable())
std::terminate();
}


所以如果join后不置空,会抛异常。

回头看__gthread_join

1
2
3
4
static inline int __gthread_join(__gthread_t __threadid, void **__value_ptr) {
return __gthrw_(pthread_join)(__threadid, __value_ptr);
}

转到pthread_join

3 detach·

detach转到pthread_detach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void thread::detach() {
int __e = EINVAL;

if (_M_id != id())
__e = __gthread_detach(_M_id._M_thread);

if (__e)
__throw_system_error(__e);

_M_id = id();
}

static inline int __gthread_detach(__gthread_t __threadid) {
return __gthrw_(pthread_detach)(__threadid);
}

4 move语义支持·

1
2
3
4
5
6
7
8
9
10
11
12
thread(const thread &) = delete;

thread(thread &&__t) noexcept { swap(__t); }

thread &operator=(const thread &) = delete;

thread &operator=(thread &&__t) noexcept {
if (joinable())
std::terminate();
swap(__t);
return *this;
}

📌thread不支持copy语义,仅支持move语义, 且move operator=的前提是,当前线程已经不是joinable的,否则会有资源泄露。

5 this_thread·

还有个经常用到的功能是 std::thread::this_thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace this_thread {
/// get_id
inline thread::id get_id() noexcept {
#ifdef __GLIBC__
// For the GNU C library pthread_self() is usable without linking to
// libpthread.so but returns 0, so we cannot use it in single-threaded
// programs, because this_thread::get_id() != thread::id{} must be true.
// We know that pthread_t is an integral type in the GNU C library.
if (!__gthread_active_p())
return thread::id(1);
#endif
return thread::id(__gthread_self());
}

/// yield
inline void yield() noexcept {
#ifdef _GLIBCXX_USE_SCHED_YIELD
__gthread_yield();
#endif
}

本质上this_thread是一个namespace, 提供了一些常用功能,如 get_idyield

get_id转到:

1
2
3
4
static inline __gthread_t __gthread_self(void) {
return __gthrw_(pthread_self)();
}

yield实现转到

1
2
static inline int __gthread_yield(void) { return __gthrw_(sched_yield)(); }

6 总结·

本文深入分析了C++11标准库中的std::thread类,基于GCC-8.5.0源码。

文中详细剖析了std::thread的内部工作原理,包括构造函数、_Invoker类、_State_ptr_State_impl类等关键组成部分。这些细节揭示了线程是如何通过pthread_create系统调用来启动的,以及_Invoker如何作为线程执行的起点。同时,还讨论了joinable()join()方法的工作机制,以及线程析构时的注意事项。

最后,介绍了std::thread::this_thread命名空间提供的功能。

总的来说, gnu下的thread库,实际上是pthread_create的一层封装。

文章对你有帮助?打赏一下作者吧