为避免混淆,用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