线程的创建

线程通过构建std::thread对象而启动,std::thread的构造函数:

// 默认构造
std::thread t1;

// 使用任意可调用类型构造
std::thread t2(F&& f, Args&&... args);

// 使用另外一个线程移动构造
std::thread t3(std::move(other_thread));

可调用类型构造

普通函数

int add(int a, int b) { return a + b; }

std::thread t(add, 1, 2);

函数指针

int add(int a, int b)  {return a + b; }
int (*p_add)(int , int ) = add;
std::thread t(p_add, 1, 2);

类成员函数

若要某个类的成员函数设为线程函数,则需要将成员函数指针对象实例地址一起传递给std::thread构造函数。

class Foo {
public:
    int add(int a, int b) { return a + b; }
};

Foo foo;
std::thread t(&Foo::add, &foo, 1, 2);

lambda函数

std::thread t([](int a, int b) { return a + b; }, 1, 2);

重载函数调用符的类对象

class Functor {
public:
    int operator()(int a, int b) { return a + b; }
};
Functor f;
std::thread t(f, 1, 2);

如果f为参数列表,且传入临时变量:

class Functor {
public:
    int operator()() { std::cout << "hello" << std::endl; }
};

此时,使用std::thread t(Functor());则会被解释为:声明了一个名为t,返回值为std::thread,函数参数为一个函数指针的函数。

使用列表初始化方式可避免这种情况:std::thread t{Functor()};

std::function

std::function是一个函数对象类,它包装其它任意的函数对象:

std::function<int(int, int)> f = [](int a, int b) { return a + b; };
std::thread t(f, 1, 2);

向线程函数传递参数

线程具有内部存储空间,在传参到线程函数时:

  1. 参数按照默认方式复制,这些副本被当成临时变量。
  2. 进入到新线程的上下文环境后,以右值的形式传给新线程上的函数或可调用对象。

参数避免隐式转换

void f(int i, std::string const& s);
char buffer[256];
std::thread t(f, 3, buffer);
t.detach();

上述例子中,bufferchar*隐式转换为std::string的操作是在新线程中进行,但此时主线程可能已经销毁,buffer已经析构,所以可能会出现未定义的行为。

传递参数的引用

如上所述,参数在传递给新线程时,会传递参数的副本

class Foo { ... };

void f(int i, Foo& f);

Foo foo;

std::thread(3, foo);

在上例中,即使函数的参数为引用类型,但引用类型会被忽略,最终还是传递了参数的拷贝。 被拷贝的参数会被当成move-only类型,并以右值的形式传递。

如何解决? 使用std::ref进行包装,std::ref用来构建一个std::reference_wrapper对象并返回,该对象拥有传入变量的引用:

std::thread(3, std::ref(foo));

移动构造

std::thread支持移动语义的意义是:可以向外部移交线程的归属权

std::thread t1([](int a, int b) { return a + b; }, 1, 2);

std::thread t2 = std::move(t1);

不能赋值构造

std::thread的赋值构造函数被定义为delete,无法使用赋值语句将一个线程对象赋值给另一个线程对象。

thread(const thread&) = delete;

join

join()阻塞创建新线程的线程,直到新线程执行完毕。

此时新线程和创建新线程的线程汇合(join)。

调用join()的时机:在创建新线程的线程结束之前。

调用join()之前,必须使用joinable()判断线程是否joinable,如果一个线程曾经调用过join(),则该线程不再joinable。

detach

detach()将新线程从创建它的线程分离开来,允许新线程在后台运行。

此时新线程的归属权和控制器移交给C++ runtime library。

只用当joinable()返回true时,才能调用detach()

获取线程数量

std::thread::hardware_concurrency()返回硬件真正并发的线程数量。

int num_threads = std::thread::hardware_concurrency();

std::vector<std::thread> threads;
auto f = [](int a, int b) { return a + b; };

for(int i = 0; i < num_threads; ++i) {
    threads.emplace_back(f, 1, 2);
}

识别线程

通过线程ID来识别线程,线程ID的类型为std::thread::id

  • std::this_thread::get_id()返回当前线程的ID。
  • std::thread的成员函数get_id()返回线程对象的ID。