《C++并发编程》(C++ Concurrency in Action)读书笔记
这本书中用到很多的高级用法,如RAII等手法,因此,看这本书之前建议先阅读《Effective c++》
1.线程的启动、等待、分离。
启动
C++中的线程的启动方式和Linux中的启动方式相似,但是启动参数减少了很多。仅需传入线程初始化函数(可调用对象),以及相应的参数即可。
class background_task
{
public:
void operator()() const {
do_something(); do_something_else(); }
};
background_task f;
std::thread my_thread(f);
注意使用临时对象是,编译器将认为这是声名一个函数,而非开启一个新的线程。如下:
std::thread my_thread(background_task());
使用以下的方式可以避免这样的问题:
std::thread my_thread((background_task()));
std::thread my_thread{background_task()};
//Lamda表达式
std::thread my_thread([]{
do_something();
do_something_else();
});
有参数的函数:
void fun(int &a)
{
cout << a << endl;
}
//创建线程对象
thread t1(fun, ref(a));//fun 中的参数为引用,则在传入参数时需用ref函数,否则将会报错
t1.join();//等待主线程回收
成员函数
class X {
public:
void do_lengthy_work() {
cout << "X my_x; std::thread t(&X::do_lengthy_work, &my_x);"<< endl;
};
};
X my_x;
std::thread t(&X::do_lengthy_work, &my_x); // 1
t.join();
线程的等待回收
等待线程结束后对子线程进行资源回收(join函数)。但是必须注意异常的发生,当程序发生异常时,如果join函数未被执行,则这种情况将会导致join被跳过。所以我们会封装一个类,再该类的析构函数中调用join函数,这样即使发生异常,程序也会执行析构函数。
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::thread& t_): t(t_)
{}
~thread_guard()
{
if(t.joinable()) // 同一个线程只能进行依次join
{
t.join(); //
}
}
//禁止拷贝
thread_guard(thread_guard const&)=delete; // 3
thread_guard& operator=(thread_guard const&)=delete;
};
struct func;
void f()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
}
//当线程执行到④处时,局部对象就要被逆序销毁了。
//因此,thread_guard对象g是第一个被销 毁的,
//这时线程在析构函数中被加入②到原始//线程中。
//即使do_something_in_current_thread 抛出一个异常,这个销毁依旧会发生。
线程的分离
分离的线程将会在后台运行,因此,分离的线程将不能调用join函数,被分离的线程在在结束后由系统保证他被回收。
分离线程需要格外注意不能引用会被提前销毁的对象,如下:
struct func
{
int& i;
func(int& i_) : i(i_) {}
void operator() ()
{
for (unsigned j=0 ; j<1000000 ; ++j)
{
do_something(i); // 1. 潜在访问隐患:悬空引用
}
}
};
void oops()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach(); // 2. 不等待线程结束
}
这个例子中,已经决定不等待线程结束(使用了detach()②),所以当oops()函数执行完成时 ③,新线程中的函数可能还在运行。如果线程还在运行,它就会去调用do_something(i)函数 ①,这时就会访问已经销毁的变量。如同一个单线程程序——允许在函数完成后继续持有局部 变量的指针或引用;当然,这从来就不是一个好主意——这种情况发生时,错误并不明显, 会使多线程更容易出错。
2.所有权转移
C++标准库中有很多资源占有(resource-owning)类型, 比如 std::ifstream , std::unique_ptr 还有 std::thread 都是可移动,但不可拷贝。这就是说线程可以转移所有权,按时不能进行复制。
void some_function();
void some_other_function();
std::thread t1(some_function); // 1
std::thread t2=std::move(t1); // 2
t1=std::thread(some_other_function); // 3 为什么不显式调用 std::move() 转 移所有权呢?
//因为,所有者是一个临时对象——移动操作将会隐式的调用。
std::thread t3; // 4
t3=std::move(t2); // 5
t1=std::move(t3); // 6 赋值操作将使程序崩溃 ,t1已经有了一个关联 的线程(执行some_other_function的线程),
//所以这里系统直接调用 std::terminate() 终止程 序继续运行。
当所有权可以在函数内部传递,就允许 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类的实例,并且拥有 其线程的所有权。
移动语义的用法:
class scoped_thread
{
std::thread t;
public:
explicit scoped_thread(std::thread t_): // 1
t(std::move(t_))
{
if(!t.joinable()) // 2
throw std::logic_error(“No thread”);
}
~scoped_thread()
{
t.join(); // 3
}
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))); // 4
do_something_in_current_thread();
}
创建线程组:
void do_work(unsigned id);
void f()
{
std::vector<std::thread> threads;
for(unsigned i=0; i < 20; ++i)
{
threads.push_back(std::thread(do_work,i)); // 产生线程, 临时对象,将会调用移动语义
}
std::for_each(threads.begin(),threads.end(),
std::mem_fn(&std::thread::join)); // 对每个线程调用join()
}
3.线程标识
第一种,可以通过调 用 std::thread 对象的成员函数 get_id() 来直接获取。如果 std::thread 对象没有与任何执 行线程相关联, get_id() 将返回 std::thread::type 默认构造值,这个值表示“无线程”。第二 种,当前线程中调用 std::this_thread::get_id() (这个函数定义在 头文件中)也可以 获得线程标识。
当用线程来分割一项工 作,主线程可能要做一些与其他线程不同的工作。这种情况下,启动其他线程 前,它可以将自己的线程ID通过 std::this_thread::get_id() 得到,并进行存储。就是算法核 心部分(所有线程都一样的),每个线程都要检查一下,其拥有的线程ID是否与初始线程的ID相 同。
std::thread::id master_thread;
void some_core_part_of_algorithm()
{
if(std::this_thread::get_id()==master_thread)
{
do_master_thread_work();
}
do_common_work();
}