为避免混淆,用thread表示
std::thread
及其对象实例,用线程表示操作系统概念下的线程
Chapter 2 thread
的管理
2.1 thread
的创建(构造函数)
a. 默认构造函数
default: thread() noexcept;
创建一个placeholder,不和任何线程关联。其使用场景如定义一个thread
数组。可以在之后通过thread& operator=( thread&& other ) noexcept
来赋予值以和线程关联
b. Move构造函数
thread (thread&& other) noexcept;
c. 初始化构造函数
template<class Function, class...Args>
explicit thread(Function&& f, Args&&...args);
常用的构造函数,在创建对象的时候传入function及其参数,从而和线程关联
d. 拷贝构造函数
thread(const thread&) = delete;
thread
对象不可拷贝
2.2 join
:等待线程执行完成
join
也会清理和线程有关的所有内存。join
返回后thread
不再与任何线程关联,joinable()
将返回false
。每个线程只能调用一次join
异常可能使程序在join
被调用前结束,为了避免这种情况,需要在catch
中也调用join
。但 try-catch 的方式过于繁琐,而且会打乱作用域。因此如果确实有要保证在所有退出路径均join
的需求,可以采用 RAII 机制,封装一个 thread_guard 类,在其析构函数中处理
2.3 detach
:后台线程
对thread
对象调用detach
将使与之关联的线程在后台运行,且断绝了任何能与之通信的手段,无法再通过thread
对象来引用它,也因此无法被join
被 detach 的线程又叫做 daemon 线程
thread
对象是否可被 detach
和其是否可以 join
需满足相同的条件:必须有线程与之关联,体现在joinable()
必须返回true
2.4 thread
参数传递
构造thread
对象时在传入 callable 之后紧接着传入其所需参数
参数被拷贝到可以被新创建的线程访问到的内部存储中,然后作为右值传给 callable,如同临时变量一样
如果传参以来隐式转型,则结果可能并非所料。如参数为string const&
,传入的为char buff[LEN]
时,虽然看起来是buf
会隐式转型为string const
,然后再传给 callable,但实际上传参时是 as is,即不加处理、先将传入的参数(此处是 buf
(指针/数组名))拷贝到内部存储,此时若thread
对象被detach
,则buf
指向的存储空间被清理,而此时 callable 的参数可能未来得及构造出来,从而会出现未定义的行为
直接传 non-const reference 的话会编译出错,因为构造函数接受的参数类型为右值,如果想传引用,需用std::ref
如果参数类型不能被拷贝只能被 move,则传参后控制权会转移
2.5 thread
ownership的转移
thread
对象实例可以被 move,可以被 move 构造
不可 move 给已经同线程关联的thread
2.6 std::thread::hardware_concurrency()
:一般为CPU核数,但只是 hint 值,也可能为0
2.7 thread
的标识
线程标识类型:std::thread::id
,可进行比较,可哈希,但其值没有语义涵义(无意义)
调用thread
对象实例的get_id()
方法获取 id。若未与线程相关联,则返回默认构造的 id,表示 not any thread
当前线程的 id 可以通过std::this_thread::get_id()
获取 id