管理线程
Recall
传入thread的参数?临时对象有什么问题?
线程detach引用了主线程的资源?
异常情况下何时调用join?
std::thread 传入 引用?
std::bind? std::decay?
std::ref?
转移线程所有权
thread 是可以被move的,但是不能被copy
比thread_guard更好的scoped_thread?
好在哪里
运行时选择线程数量?
Notes
传入thread的参数?临时对象有什么问题?
- 可以是一个重载了operator()的struct
class background_task{
public:
void operator() const{
do_something();
}
};
background_task f = background_task();
std::thread t(f);
//使用临时对象,容易报错
std::thread my_thread(background_task()); // 会解析为函数
//正确用法
std::thread my_thread((background_task()));
std::thread my_thread{background_task()};
线程detach引用了主线程的资源?
当线程被detach后,与主线程的生命周期分离,但是如果线程还在引用主线程里的变量,要小心主线程已经结束,释放了那些对象。
处理这种情况的通用做法是让线程函数自包含(译注:也就是函数本身什么都有,不需要依赖外部数据),将数据拷贝到线程中,而非共享数据。
异常情况下何时调用join?
主线程调用子线程的join,要在主线程抛出异常前的对方调用。也需要在异常处理过程中调用join(),从而避免意外的生命周期问题。
-
使用try catch
class background_task; void f(){ int local_var = 0; std::thread t({background_task()}); try{ do_something_in_current_thread(); }catch(...){ t.join(); throw; } t.join(); }
-
使用RAII
#include <iostream> #include <thread> struct func; class thread_guard{ private: std::thread &t; public: explicit thread_guard(std::thread& t): t(t){ } ~thread_guard(){ if(t.joinable()){ t.join(); } } thread_guard(const thread_guard& t) = delete; thread_guard& operator=(const thread_guard&) = delete; }; struct functor; void f(){ int local_state = 0; struct functor myfunc(); std::thread t(myfunc); thread_guard g(t); do_some_thing_in_current_thread(); }
std::thread 传入 引用?
class widget{
};
void update_data(widget& data);
void f2(){
widget data;
std::thread t(update_data, data);
// 编译不通过,尽管update_data的第一个参数期待传入一个引用,
// 但是std::thread的构造函数并不知晓;
//构造函数无视函数期待的参数类型,并盲目地拷贝已提供的变量。
}
// 正确用法;类似std::bind
std::thread t(update_data_for_widget,w,std::ref(data));
/*
std::bind always copies its arguments, but callers can achieve the
effect of having an argument stored by reference by applying std::ref to it.
*/
Simply speaking, std::decay can remove references from type. For example, std::decay<int&>::type is the same as just int. For std::thread and std::bind, when they store the parameters you passed in, std::decay
#include <iostream>
#include <type_traits>
using namespace std;
int main()
{
int a = 0;
int& a_ref = a;
decay<int&>::type a_copy = a_ref;
a_ref++;
cout << a << endl;
cout << a_copy << endl;
}
//output
/*
0
1
*/
std::ref 可以让引用变得 copyable
To overcome what std::decay
does to references, we must have a copiable object which stores a reference internally. This is exactly what [std::reference_wrapper](https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper)
does.
std::reference_wrapper is a class template that wraps a reference in a copyable, assignable object. It is frequently used as a mechanism to store references inside standard containers (like std::vector) which cannot normally hold references.
[std::ref](https://en.cppreference.com/w/cpp/utility/functional/ref)
is a handy function in the standard library to create a std::reference_wrapper
object. With std::ref
, we can finally pass references as parameters correctly as shown below (continuing the example above).
void inc(int& x)
{
x++;
cout << "Inside inc, x is now " << x << endl;
}
int main()
{
int a = 0;
int b = 0;
auto func1 = bind(inc, a);
func1();
cout << "After func1, value of a is " << a << endl;
auto func2 = bind(inc, ref(b));
func2();
cout << "After func2, value of b is " << b << endl;
}
/*output
Inside inc, x is now 1
After func1, value of a is 0
Inside inc, x is now 1
After func2, value of b is 1
*/
转移线程所有权
#include <iostream>
#include <thread>
void func(){
std::cout << "func" << std::endl;
}
int main(){
std::thread t(func);
std::thread& g = t; //right
// std::thread g = t; //wrong
g.join();
std::cout << "main" << std::endl;
}
比thread_guard更好的scoped_thread?
class scoped_thread{
private:
std::thread t_;
public:
scoped_thread(std::thread t): t_(std::move(t)){
if(!t_.joinable()){
throw std::logic_error("No thread");
}
}
~scoped_thread(){
t_.join();
}
scoped_thread(const scoped_thread&) = delete;
scoped_thread& operator=(const scoped_thread&) = delete;
};
struct func;
void f(){
int some_local_state = 0;
scoped_thread s(std::thread(func(some_local_state)));
do_some_thing_in_current_thread();
}
- 避免了当thread_guard的生命周期长于它所引用的线程时引起不愉快的后果?
- 还意味着,一旦所有权转移到这个对象以后,没有谁能连接或者分离这个线程(因为thread_guard是传入一个引用,在main thread里仍然可以对那个thread进行操作,而scoped_thread是直接把thread move进scoped_thread对象里了)。
运行时选择线程数量?
std::thread::hardware_concurrency()
SUMMARY: 管理c++的线程,要注意thread不能copy,而且要考虑何时join(thread_guard, scoped_thread)