1.堤防函数已经结束,线程依然访问局部变量

看以下例子:

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. 不等待线程结束 
 } //

3. 新线程可能还在运行
这个例子中 some_local_state是一个局部变量,在函数退出后局部变量已经不存在了,但是如果线程尚未结束的话,会调用不存在的局部变量,发生错误。因此使用一个能访问局部变量的函数去创建线程是一个糟糕的注意。

2.等待线程完成

如果需要等待线程,调用std::thread的join()函数,上述例子如果将my_thread.detach()替换为my_thread.join(),就可以确保局部变量在线程完成后才被销毁。这种情况下原始线程在其生命周期中没有什么事可做,使得用一个独立的线程去执行函数变得收益甚微,但在实际编程中,原始线程要么有自己的工作要
做;要么会启动多个子线程来做一些有用的工作,并等待这些线程结束。

3.特殊情况下的等待
需要对一个还未销毁的 std::thread 对象使用join()或detach()。如果想要分离一个线程,可以在线程启动后,直接使用detach()进行分离。如果打算等待对应线程,则需要细心挑选调用join()的位置。当在线程运行之后产生异常,在join()调用之前抛出,就意味着很这次调用会被跳过。
等待线程完成:

struct func; // 定义在清单2.1中 
 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(); // 1 
 throw; 
 } 
 t.join(); // 2 
 }


上述例子在线程正常退出后,会跳到2处。如果抛出异常,则会执行到1处。以此保证线程退出后,函数才结束。

还可以采用一致RAII的方式,提供一个类,在析构函数中使用join();

class thread_guard 
 { 
 std::thread& t; 
 public: 
 explicit thread_guard(std::thread& t_): 
 t(t_) 
 {} 
 ~thread_guard() 
 { 
 if(t.joinable()) // 1 
 { 
 t.join(); // 2 
 } 
 } 
 thread_guard(thread_guard const&)=delete; // 3 
 thread_guard& operator=(thread_guard const&)=delete; 
 }; 
 struct func; // 定义在清单2.1中 
 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(); 
 } // 4 
 当线程执行到4处时,局部对象要被逆序销毁了。因此thread_guard对象g时第一个被销毁的,这是线程在析构函数中被加入2到原始线程中,即使线程中抛出异常,这个销毁依旧会发生。joinable时标记当前线程是否已加入到1中,因为join只能对给定的对象调用一次,所以对给已加入的线程在此进行加入操作时会导致错误。 
 4.后台运行线程 
 后台线程也被成为守护线程。这类线程的特点是发后即忘. 
 std::thread t(do_background_work); 
 t.detach(); 
 assert(!t.joinable()); 
 例如让多文档同时编辑多个文档,可以设计成这里就可以让文档处理窗口运行在分离的线程上。 
 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); // 1 
 t.detach(); // 2 
 } 
 else 
 { 
 process_user_input(cmd); 
 } 
 }


如果用户选择打开一个新文档,为了让迅速打开文档,需要启动一个新线程去打开新文档①,并分离线程②。与当前线程做出的操作一样,新线程只不过是打开另一个文件而已。所以,edit_document函数可以复用,通过传参的形式打开新的文件。这个例子也展示了传参启动线程的方法:不仅可以向std::thread 构造函数①传递函数名,还可以传递函数所需的参数(实参)