C++11 并发实战阅读笔记(1)

来源:互联网 发布:汽车配件淘宝店铺名字 编辑:程序博客网 时间:2024/04/29 22:18

                    第二章 managing threads


std::thread 的用法:

void do_some_work(); std::thread my_thread(do_some_work);

还可以使用函数对象,函数对象就是重载了()操作符的类,可以用来形如 A() 来达到调用函数的效果

class background_task{public:void operator()() const{  do_something();  do_something_else();}};background_task f;std::thread my_thread(f);   // f()就表示调用了()操作符,这里就相当于执行代码do_something()和do_something_else()

这种用法有个陷进:

std::thread my_thread(background_task()); // 这里不会启动线程,而是将my_thread当成了一个返回thread对象的函数
这个陷阱叫做“C++’s most vexing parse”, 具体解释可以见Effective STL 或者如下链接

http://www.cnblogs.com/yangyingchao/p/3394146.html

解决办法:

std::thread my_thread((background_task()));std::thread my_thread{background_task()}; // C++11 变量的初始化方法

或者使用C++11里的lambda函数:

std::thread my_thread([]{(do_something();do_something_else();});


list 2.1: 线程对函数局部变量的访问非法

struct func{  int& i;    func(int& i_):i(i_){}  <span style="font-family: Courier;">// 这里是一个引用的i的初始化</span>  void operator()()  {  for(unsigned j=0;j<1000000;++j)  {    do_something(i);  }  }};void oops(){  int some_local_state=0;  func my_func(some_local_state);  std::thread my_thread(my_func);  my_thread.detach();  // 使线程脱离,即这里不用等待线程的结束,如果换成my_thread.join()则没问题,因为join会阻塞,知道my_thread线程结束才会返回}  //函数执行完毕以后,线程可能依旧还在执行,这时候对some_local_state的访问就是非法的

当心在等待线程结束的时候, 如果在调用join()之前,程序发生异常,则有可能调用不到join()!

常规解决办法是在catch里面join():

struct func;void f(){  int some_local_state=0;  func my_func(some_local_state);  std::thread t(my_func);  try  {    do_something_in_current_thread();  }  catch(...)  {    t.join();</span>    throw;  }  t.join();}


较优的解决方案是使用RAII,这样代码较简洁,也不容易出错

class thread_guard{  std::thread& t;  public:  explicit thread_guard(std::thread& t_):t(t_){}    ~thread_guard()  {    if(t.joinable())   //注意 这个判断是必要的 因为一个thread只能被调用一次join(),增加该判断是防止线程在别的地方已经被join()了!    {      t.join();    }  }  thread_guard(thread_guard const&)=delete; // c++11的用法   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();  }
说明:因为t是局部变量,当程序退出f()的作用域时t会析构,从而调用join()达到等待线程结束的目的。


调用detach()方法可以使线程在后台运行,从而无法再joinable();

std::thread t(do_background_work);t.detach();assert(!t.joinable());

多文档操作是detach的典型实例:

void edit_document(std::string const& filename){  open_document_and_display_gui(filename);  while(!done_editing())  {    user_command cmd=get_user_input();    if(cmd.type==open_new_document)    {      std::string const new_name=get_filename_from_user();      std::thread t(edit_document,new_name);      t.detach();    }    else    {        process_user_input(cmd);    }  }}
参数传递,线程在传递参数的时候是采用拷贝的方式,所以在传参的时候当心悬垂指针

void f(int i,std::string const& s);void oops(int some_param){   char buffer[1024];   sprintf(buffer, "%i",some_param);   std::thread t(f,3,buffer);     t.detach();}
线程在将buffer转化为string&的时候可能oops已经执行完毕退出,从而造成buffer是悬垂指针

解决办法:

void f(int i,std::string const& s);void not_oops(int some_param){  char buffer[1024];  sprintf(buffer,"%i",some_param);  std::thread t(f,3,std::string(buffer));  //显示转换  t.detach();}
同时由于线程是使用拷贝的方式传参,所以在传递给线程来更新的数据则要传递引用:

void update_data_for_widget(widget_id w,widget_data& data);void oops_again(widget_id w){  widget_data data;  std::thread t(update_data_for_widget,w,data);  // 这里对data进行拷贝传值  display_status();  t.join();  process_widget_data(data);  //由于是传值 所以这里的数据没有更新 还是原来的值}
解决办法是使用std::ref() :

std::thread t(update_data_for_widget,w,std::ref(data)); //传递的是data的引用
还可以传递类方法给thread对象:

class X{  public:  void do_lengthy_work();};  X my_x;  std::thread t(&X::do_lengthy_work,&my_x);  // 最后线程调用:my_x.do_lengthy_work()

对于不能进行拷贝的对象,则要使用move方法,例如uniqe_ptr对象,uniqe_ptr不能复制,只能转移即move;

void process_big_object(std::unique_ptr<big_object>);std::unique_ptr<big_object> p(new big_object);p->prepare_data(42);std::thread t(process_big_object,std::move(p));
同样的std::thread对象也是只能move而不能copy的对象,所以一个线程同时只能和一个thread对象关联,但是却可以在thread对象之间用move传递,当线程被move以后,就成了一个空对象(没有线程与之关联);

void some_function();void some_other_function();std::thread t1(some_function);std::thread t2=std::move(t1);  // t2显现在关联<span style="font-family: Courier;">some_function的线程,t1悬空</span>t1=std::thread(some_other_function); // t1关联新创建的线程,注意这里的t1在赋值前是空的std::thread t3; //声明空的线程对象t3t3=std::move(t2); //将t2的线程传递给t3,t2悬空t1=std::move(t3);//将t3的线程传递给t1,注意这里的t1在赋值前不是空线程对象,而是有关联线程,在赋值执行完之后,会导致std::terminate()被调用,从而使整个程序停                  止,因为线程只能被join或者detach,而不能被丢弃(t1=std::move(t3)就相当于丢弃原先t1的线程)。
thread对象的move特性是它可以作为函数的返回值:

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;}

当将thread作为参数传递的时候自然要使用move方法了:

void f(std::thread t);void g(){  void some_function();  f(std::thread(some_function));  std::thread t(some_function);  f(std::move(t));}

scope_thread(注意与前面的RAII的不同),这个类构造的时候是直接持有线程,并不是引用:

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();}
可以用vector来管理线程,因为有move特性,vector就直接持有该线程对象

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));    //std::mem_fn 是让循环的对象每个都调用一次某个方法,可以是对象的成员函数,这里是std::thread::join方法}
几个有用的std::方法

std::accumulate    // 求和
std::distance   // 迭代器之间的距离(长度)
std::thread::hardware_concurrency   // 当前系统最大线程并行数
线程的身份证,std::thread::id,该id支持拷贝和比较,当id相等时可以判断2个线程相同(要么是同一个线程,要么都是没有关联线程的空的std::thread对象),因为该id满足完全的比较特性,如a>b,b>c 则 a>c, 所以可以用来sort排序,并且还支持std::hash<std::thread::id> ,即可以用来hash!

获取id的方法有2种:1 使用thread对象调用方法get_id(),当thread对象没有与线程关联,则该方法返回一个默认构造的id对象,该对象可以用来说明该对象是"not any thread"; 2 是在当前线程调用方法std::this_thread::get_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();}
该id还可用来输出:

std::cout<<std::this_thread::get_id();
同一个id输出的内容是一样的!









0 0
原创粉丝点击