在C++11之前,多线程我们一般使用ptread库。C++11提供了一个很好用于线程操作的标准库std::thread,编码时需引入头文件#include。由于Unix平台std::thread底层实现仍旧是pthread, 所以需要增加编译参数 -lpthread 。

一,构造函数
1. 默认构造函数
thread() noexcept:
默认构造函数,创建一个空的 std::thread 执行对象。
2. 初始化构造函数
template <class Fn, class… Args>
explicit thread(Fn&& fn, Args&&… args);

创建一个 std::thread 对象,该 std::thread 对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
3. 拷贝构造函数
thread(const thread&) = delete;
(被禁用),意味着 std::thread 对象不可拷贝构造。
4. Move 构造函数
thread(thread&& x) noexcept;
move 构造函数(move 语义是 C++11 新出现的概念,详见附录),调用成功之后 x 不代表任何 std::thread 执行对象。

二,std::thread的使用注意事项
1.std::thread不可复制
std::thread thread1(task);
std::thread thread2 = thread1; // 不可复制,将会编译报错
2.std::thread在栈内创建后,需要在同一个作用域内调用join() 或者 detach(), 否则退出作用域后,程序会异常退出。
原因是std::thread创建后默认是joinable的,虚构函数调用了std::terminate来终止进程的调用。
~thread(){
if (joinable())
std::terminate();
}
因此在创建线程后需要调用join()或者detach() 来使线程变成非joinable的状态, 具体使用join()还是detach() 取决于实际需求, 如果需要等待线程完成才能继续,那就使用join()来等待, 如果需要立刻返回继续其他操作, 那就调用detach()来脱离对线程的管理, 两者必须选一个。

3.调用new 创建的std::thread, 禁止直接使用delete。
需要在调用delete之前,调用 join()或者 detach() (如果创建线程后立刻调用了这两个函数中任意一个, 可以不再调用, 但是为了保险起见, 需要加上if(joinable()) 的判断)。

三、线程传参及注意事项
线程参数的传递一般采用以下三种之一的形式:
1.函数参数传递
在创建thread object时可以向线程传递参数,默认情况下,参数会被拷贝到线程空间以供线程执行时存取,即使参数是引用也是这样,这种机制会引发一些问题。
问题1.临时参数的未及时构造

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); //这里的buffer指向的是一个栈上的指针,所以在线程真正要执行这个函数f的时候,这个栈可能已经释放了,因此会存在异常
    t.detach();
}

虽然函数f的第二个参数接受的是一个std::string,但是我们传递进去的是一个char*,主线程退出时,buffer会被销毁,但是如果此时在子线程中buffer还没有被转化成string,就会出现异常。

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,std::string(buffer)); //这里首先将buffer构造成string传进去
    t.detach();
}

这种做法保证了现将buffer构造好string,随后线程传递的时候传递string就不会有这个问题。

问题2.传递引用时无法修改引用的数据

void update_data_for_widget(widget_data& data);//函数接受的参数是引用
void oops_again()
{
    widget_data data;
    std::thread t(update_data_for_widget,data);//试图在异步线程中修改data
    t.jion();
   process_widget(data);
}

虽然update_data_for_widget这个函数接受的是引用,可以修改data的值。但是当我们把data传入std::thread的构造函数时,拷贝构造就已经发生了。也就是说,最后update_data_for_widget这个函数处理的只是一份拷贝,根本就不会修改原始的值。所以在这种场景下,我们可以采用以std::ref的形式传递引用。相当于在引用上面做了一层对象封装,单纯的传递对象依旧可以修改原始的值:

std:thread t(update_data_for_widget,std::ref(data));

2.对象的函数形式

void ThreadTest::CreateThread(){   
  // 创建一个分支线程,回调到myThread函数,myThread无参数 
  std::thread thread1(&ThreadTest::myThread,this);
  // 有参数
  std::thread thread2(&ThreadTest::myThreadParam, this, 10, 20);
}

void ThreadTest::myThread(){
  printf("in my thread");
}
void ThreadTest::myThreadParam(int n, int m ){
  printf("in myThreadParam");
}

3.Lambda函数

我们可以用lambda函数(匿名函数)这样替换线程函数:

int main()
{
  std::thread t([]()
  {
    std::cout << "thread function\n";
  }
  );
  std::cout << "main thread\n";
  t.join();   // main thread waits for t to finish
  return 0;
}