C++11引入了std::thread,用于多线程编程。

本文从实际应用的角度,分享一些常见的std::thread基础用法。

创建线程

我们从一个最简单的例子入手:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <thread>
void hello()
{
std::cout << "Hello std::thread!" << std::endl;
}
int main()
{
std::thread t(hello);
t.join();
}

上面的例子中我们做了四件事:

  • 引入了头文件#include <thread>
  • 编写了一个独立的函数hello,它没有参数,没有返回值
  • 创建了std::thread对象,它的参数是函数指针hello
  • 调用了std::thread的join成员函数,join会等待线程直到执行完成,我们在稍后会介绍它

构造一个std::thread对象

从上面最简单的例子可以看出,我们通常这样构造一个线程对象

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

其实,可以使用任何可Callable的类型,来构造std::thread,比如函数对象:

1
2
3
4
5
6
7
8
9
10
11
class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);

使用Lambda

或者我们可以使用匿名函数,就像下面这样:

1
2
3
4
std::thread my_thread([](
do_something();
do_something_else();
});

join & detach

在上面最简单的例子中,我们知道了,使用join()可以等待线程直到执行完成。

1
2
3
4
5
6
7
std::thread t1([]() {
for (int i = 0; i < 10; i++) {
std::cout << "in t1: " << i + 1 << std::endl;
}
});
t1.join();
std::cout << "t1 done." << std::endl;

它的输出是

1
2
3
4
5
6
7
8
9
10
11
in t1: 1
in t1: 2
in t1: 3
in t1: 4
in t1: 5
in t1: 6
in t1: 7
in t1: 8
in t1: 9
in t1: 10
t1 done.

直到t1线程执行完成后,才打印出t1 done

与join对应的,detach用来在后台运行线程。

我们尝试把上面例子的join改成detach

1
2
3
4
5
6
7
8
std::thread t1([]() {
for (int i = 0; i < 10; i++) {
std::cout << "in t1: " << i + 1 << std::endl;
}
});
t1.detach();
std::cout << "t1 done." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));

打印的结果变成了

1
2
3
4
5
6
7
8
9
10
11
t1 done.
in t1: 1
in t1: 2
in t1: 3
in t1: 4
in t1: 5
in t1: 6
in t1: 7
in t1: 8
in t1: 9
in t1: 10

传参

在std::thread构造函数中添加额外的参数就好了。

1
2
3
4
5
void foo(int m, int n)
{
std::cout << m << n << std::endl;
}
std::thread t(foo, 5, 4);

注意,给类的成员函数传参,要加上对象指针。就像下面这样:

1
2
3
4
5
6
7
8
struct Foo {
void bar(int n) {
std::cout << n << std::endl;
}
};
Foo foo;
std::thread t(&Foo::bar, &foo, 5);
t.join();

返回值

我们如何从线程中拿到返回值呢?

Lambda + 引用捕获

1
2
3
4
5
6
int r = 0;
std::thread t([&r]() {
r = 10;
});
t.join();
std::cout << r << std::endl; // 10

使用std::promise和std::future

std::promise和std::future提供了一种可以从异步方法中拿到执行结果的机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <thread>
#include <future>
void foo(int m, int n, std::promise<int>&& p)
{
p.set_value(m * n);
}
int main()
{
std::promise<int> p;
auto f = p.get_future();
std::thread t(&foo, 5, 6, std::move(p));
t.join();
int i = f.get();
std::cout << i << std::endl; // 30
}

使用std::async

使用std::async可以简化上面例子的写法,它异步的执行一个函数,返回std::future类型的结果。

上面的例子可以写成:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <thread>
#include <future>
int foo(int m, int n)
{
return m * n;
}
int main()
{
auto f = std::async(foo, 5, 6);
std::cout << f.get() << std::endl;
}

在线程之间共享数据

假设我们用一个单例来管理这些共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Singleton {
public:
static Singleton& instance()
{
static Singleton s;
return s;
}
void add(int n)
{
v_.emplace_back(n);
}
int count() const
{
return v_.size();
}
private:
Singleton() {}
public:
Singleton(Singleton const&) = delete;
void operator=(Singleton const&) = delete;
private:
std::vector<int> v_;
};
int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(std::thread([](int i) {
Singleton::instance().add(i);
}, i));
}
for (auto& t : threads) {
t.join();
}
std::cout << Singleton::instance().count() << std::endl;
}

我们同时run了10个线程,每个线程往池子里丢一个数据。理论上应该是10个,但实际上可能并非如此。

加锁

我们对上面的Singleton稍作修改,加一个互斥量,并对往池子丢数据的地方加锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Singleton {
public:
static Singleton& instance()
{
static Singleton s;
return s;
}
void add(int n)
{
std::lock_guard<std::mutex> guard(mutex_);
v_.emplace_back(n);
}
int count() const
{
return v_.size();
}
private:
Singleton() {}
public:
Singleton(Singleton const&) = delete;
void operator=(Singleton const&) = delete;
private:
std::mutex mutex_;
std::vector<int> v_;
};

这样,我们就能得到期望的结果了。

终止线程

有可能我们在关闭主程序时,我们的子线程还没结束。我们需要正常的结束子线程。

加个标志

1
2
3
4
5
6
7
8
9
10
std::atomic_bool stop_flag = false;
void foo()
{
while (true) {
if (stop_flag) {
return;
}
// do something
}
}

然后在主线程中改变这个标志

1
2
3
4
std::thread t(foo);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 让子弹飞一会儿
stop_flag = true;
t.join();

其他

同时跑几个线程合适?

推荐使用std::thread::hardware_concurrency(), 在多核架构的运行环境上,这个返回值一般对应核的颗数。

获取当前线程的标识

使用std::this_thread::get_id()

std::async支持两种策略

默认使用std::launch::async, 惰性调用时使用std::launch::deferred

文章中没提到的

本文介绍的只是基础的多线程使用。有很多主题都没有介绍,比如如何在多个线程中进行同步,原子操作等等。

Reference