Transferring ownership of a thread
有时我们需要创建一个线程执行后台任务,但是创建线程的函数不等待线程执行完成而是把线程的所有权给调用者;或者相反,创建一个线程,然后把所有权给某个函数。这就涉及到线程的所有权转移。
std::thread对移动的支持就是为了解决这个问题。执行线程的所有权可以在不同std::thread对象之间转移。下面的例子就创建了两个线程和三个std::thread对象。
void some_function();
void some_other_function();
std::thread t1(some_function);
std::thread t2 = std::move(t1);
t1 = std::thread(some_other_function);
std::thread t3;
t3 = std::move(t2);
t1 = std::move(t3);
t1关联。接着,在构造t2的时候,通过显式的std::move()把线程的所有权转移给t2。现在t1不再关联任何执行线程,t2和运行some_function的线程关联。
接着,启动新的线程,和临时std::thread对象关联。后续所有权转移给t1不需要调用std::move(),因为所有者是一个临时对象,从临时对象转移所有权是自动的(隐式的)。
使用默认构造函数构造t3,没有和任何线程关联。接着把t2关联的线程所有权转移给t3,这里是显式通过std::move()进行的,因为t2是具名变量。此时,t1关联运行some_other_function的线程,t2没有关联任何线程,t3关联运行some_function的线程。
最后,把运行some_function的线程的所有权转移回t1。但是t1已经关联一个线程了,那么会调用线程的析构函数,但是此线程没有join()或者detach(),那么析构函数会调用std::terminate()终止程序。赋值新的线程对象会导致丢弃旧的线程对象。
std::thread对移动的支持意味着我们可以轻松的把线程所有权转移出函数。
std::thread f()
{
void some_function();
return std::thread(some_function);
}
std::thread g()
{
void some_other_function(int);
std::thread t(some_other_function, 42);
return t;
}
std::thread示例作为参数,那么我们可以转移所有权到函数内。
void f(std::thread t);
void g()
{
void some_function();
f(std::thread(some_function));
std::thread t(some_function);
f(std::move(t));
}
std::thread所有权可以转移使得我们可以转移线程所有权到thread_guard类内部,以免发生任何不期待的行为,因为外部没有人能够join()或者detach()这个线程了。我们重写一个类似的scoped_thread类,目标是在退出某个范围前线程完成。
class scoped_thread
{
std::thread t;
public:
explicit scoped_thread(std::thread t_) : t(std::move(t_))
{
if (!t.joinable())
throw std::logic_error("No thread");
}
~scoped_thread()
{
t.join();
}
scoped_thread(scoped_thread const &) = delete;
scoped_thread &operator=(scoped_thread const &) = delete;
};
struct func;
void f()
{
int some_local_state;
scoped_thread t{std::thread(func(some_local_state))};
do_something_in_current_thread();
}
f执行完的时候,t会被销毁,这时在析构函数中join()。thread_guard是在析构函数中检查joinable(),这里修改成了在构造函数里面检查。
C++17 有一个提案,joining_thread,当线程析构时自动join(),和上面的代码很类似,不过委员会未能达成共识。不过C++20 的时候,std::jthread是标准的一部分了。实现类似下面的代码。
class joining_thread
{
std::thread t;
public:
joining_thread() noexcept = default;
template <typename Callable, typename... Args>
explicit joining_thread(Callable &&func, Args &&...args) : t(std::forward<Callable>(func), std::forward<Args>(args)...)
{
}
explicit joining_thread(std::thread t_) noexcept : t(std::move(t_))
{
}
joining_thread(joining_thread &&other) noexcept : t(std::move(other.t))
{
}
joining_thread &operator=(joining_thread &&other) noexcept
{
if (joinable())
join();
t = std::move(other.t);
return *this;
}
joining_thread &operator=(std::thread other) noexcept
{
if (joinable())
join();
t = std::move(other);
return *this;
}
~joining_thread() noexcept
{
if (joinable())
join();
}
void swap(joining_thread &other) noexcept
{
t.swap(other.t);
}
std::thread::id get_id() const noexcept
{
return t.get_id();
}
bool joinable() const noexcept
{
return t.joinable();
}
void join()
{
t.join();
}
void detach()
{
t.detach();
}
std::thread &as_thread() noexcept
{
return t;
}
const std::thread &as_thread() const noexcept
{
return t;
}
};
std::thread的移动支持使得支持移动操作的容器(比如std::vector<>)可以放std::thread对象。可以像下面的例子一样生成一堆线程放到容易内然后等待它们结束。
void do_work(unsigned id);
void f()
{
std::vector<std::thread> threads;
for (unsigned i = 0; i < 20; ++i)
{
threads.emplace_back(do_work, i);
}
for (auto &entry : threads)
entry.join();
}
把std::thread对象放到std::vector里面而不是一个个的声明并等待,向自动管理线程又前进了一步,这些线程可以视为一组。