intmain(int argc, char *argv[]){ std::thread t((SleepLoop(3, 1))); std::cout << "C: this statement is used to test the start time of a new thread" << std::endl; t.join(); std::cout << "B: this statement is after t.join()" << std::endl; return0; }
下面是执行了两次的输出:
1 2 3 4 5 6 7 8 9 10 11 12
[gukaifeng@iZ8vbf7xcuoq7ug1e7hjk5Z main]$ ./multi-threads A: sleep loop: time = C: this statement is used to test the start time of a new thread0, span = 1
A: sleep loop: time = 1, span = 1 A: sleep loop: time = 2, span = 1 B: this statement is after t.join() [gukaifeng@iZ8vbf7xcuoq7ug1e7hjk5Z main]$ ./multi-threads C: this statement is used to test the start time of a new threadA: sleep loop: time = 0, span = 1 A: sleep loop: time = 1, span = 1 A: sleep loop: time = 2, span = 1 B: this statement is after t.join()
我们可以看到 A 和 C 语句的输出顺序是比较混乱的,但可以看到有 A 先于 C 输出的样例。我这里不打算深究其原因,因为此现象已经足以证明当前问题。
intmain(int argc, char *argv[]){ std::cout << "main: thread id is " << std::this_thread::get_id() << std::endl;
std::thread t1((ThreadFunc())); std::thread t2((ThreadFunc())); std::cout << std::boolalpha; std::cout << " t1: thread id is " << t1.get_id() << ", and joinable() is " << t1.joinable() << std::endl; std::cout << " t2: thread id is " << t2.get_id() << ", and joinable() is " << t2.joinable() << std::endl; t1.join(); t2.detach(); std::cout << " t1: after join(), thread id is " << t1.get_id() << ", and joinable() is " << t1.joinable() << std::endl; std::cout << " t2: after detach(), thread id is " << t2.get_id() << ", and joinable() is " << t2.joinable() << std::endl; return0; }
输出如下:
1 2 3 4 5
main: thread id is 140367827535680 t1: thread id is 140367809496832, and joinable() is true t2: thread id is 140367801104128, and joinable() is true t1: after join(), thread id is thread::id of a non-executing thread, and joinable() is false t2: after detach(), thread id is thread::id of a non-executing thread, and joinable() is false
通过观察输出,再进一步解释,我们可以明白一件事:
当调用过 t.join() 或 t.detach() 时,这两个方法会将 t 中表示线程 id 的对象值改为 0,即不再代表任何线程。也就是说,一旦调用过 t.join() 或 t.detach(),t 中表示的线程 id 将失效,t 将无法再控制与其关联的线程!
t 无法再控制与其关联的线程,自然也就无法再次 join() 或 detach(),所以这俩方法只能执行一次。
还有就是,不是说 joinable() 为 false 后线程就没了,只是 t 跟这线程没关系了,线程还是那个线程,只是不再满足 “只有 t 关联的线程是活跃线程时才可以执行 t.join() 或 t.detach()” 中的“关联”二字。
std::cout << "t6 was constructed by \"std::move(t4)\", so t4's joinable() is " << t4.joinable() << ", t6's joinable() is " << t6.joinable() << std::endl; std::cout << "t7 was constructed by \"= std::move(t5)\", so t5's joinable() is " << t5.joinable() << ", t7's joinable() is " << t7.joinable() << std::endl;
t4, t4's joinable() is true t5, t5's joinable() is true
t6 was constructed by "std::move(t4)", so t4's joinable() is false, t6's joinable() is true t7 was constructed by "= std::move(t5)", so t5's joinable() is false, t7's joinable() is true
/usr/include/c++/8/thread:120:17: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues static_assert( __is_invocable<typename decay<_Callable>::type, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ typename decay<_Args>::type...>::value,
最关键的一句话是 “std::thread arguments must be invocable after conversion to rvalues”,即参数必须是可调用的或者可以转成右值的。
可调用,指的是第一个参数,就是线程的函数(或其他调用)入口。
可转成右值,就是我们刚刚报错的主要原因,因为引用不能转成右值。
我们看一下这个构造函数的源码:
1 2 3 4 5 6 7 8 9
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" );
我们知道线程是有自己的内存存储空间的,在 std::thread 类的构造中,参数会先按照默认方式复制到线程的存储空间中,然后新创建的线程才能访问它们。这些副本被当做临时变量,以右值的方式传递给新线程上的函数或者可调用对象。即便函数相关的参数按设想应该是引用(比如即便你在构造时传递的是 int const & 类型,新线程最后传递的其实还是个 int &&)。
按上面代码,参数 num 应当以引用方式传入的,但我们执行 std::thread t(func, a); 时,线程库还是会把 a 复制一份到新线程的内存存储空间中,然后将这个副本以 move-only(只能移动,不能复制) 的方式传递给一个右值给 func(),因为这个函数预期接收一个非 const 引用,右值不能转为非 const 引用,所以就会编译出错。