本文讨论C++11中新引入的对线程的支持。
std::thread
C++在11标准中增加了对线程(threads)的支持,看以下例子:
// t1.cpp
#include <thread>
#include <iostream>
using namespace std;
void f() {
cout << "hello, world!" << endl;
}
int main() {
thread t{f};
t.join();
return 0;
}
编译:把上面的代码保存为t1.cpp,并执行
g++ -std=c++11 t1.cpp -o t1
,既可以生成可执行文件t1。执行t1可以得到结果:hello, world!
使用C++11的线程功能必须包含<thread>
头文件,之后便可以使用std::thread
类来创建一个线程。创建线程的时候必须传入一个可执行体作为参数,在上面的例子中这个可执行体是函数f()
。
std::promise
为了在不同的线程之间传递数据,C++引入了std::promise和std::future这两种数据结构,在头文件<future>
中包含。
promise是一个范型的数据结构,你可以定义一个整形的promise:promise<int>
,这意味着线程之间传递的值是整形。promise的get_future()
方法返回一个future数据结构,从这个future数据结构可以获取设置给promise的值,下面是一个例子:
#include <iostream>
#include <future>
#include <thread>
using namespace std;
int main() {
promise<int> a_promise;
auto a_future = a_promise.get_future();
a_promise.set_value(10);
cout << a_future.get() << endl;
cout << "after get()" << endl;
return 0;
}
上面例子的输出结构是:
10
after get()
实际上,上面的例子并没有使用线程,但是很好得展示了promise和future之间的关系。 更复杂一点的使用场景可能是下面这样子的:
- 主线程定义一个promise,命名为p
- 主线程调用p.get_future(),并把返回值保存为引用f
- 主线程启动一个子线程,并把p作为启动参数传给子线程
- 主线程调用f.get(),但是此时子线程还未将数据放入promise内,所以主线程挂起
- 子线程执行完,获取到结果,并把结果写入p
- 主线程从f.get()的调用中被唤醒,获取到子线程写入p的值,继续执行
std::packaged_task
C++11很贴心地提供了packaged_task类型,让我们不用直接使用std::thread和std::promise,直接就能够生成线程,派遣任务:
#include <iostream>
#include <future>
using namespace std;
int f() {
string hi = "hello, world!";
cout << hi << endl;
return hi.size();
}
int main() {
packaged_task<int ()> task(f);
auto result = task.get_future();
task();
cout << result.get() << endl;
return 0;
}
上面代码的运行结果为:
hello, world!
13
std::async
std::packaged_task要求你自己启动任务,比如上一章节例子中你要显示调用task()
。如果连这一步都想省了的话,可以使用std:async:
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <future>
template <typename RandomIt>
int parallel_sum(RandomIt beg, RandomIt end)
{
auto len = end - beg;
if (len < 1000)
return std::accumulate(beg, end, 0);
RandomIt mid = beg + len/2;
auto handle = std::async(std::launch::async,
parallel_sum<RandomIt>, mid, end);
int sum = parallel_sum(beg, mid);
return sum + handle.get();
}
int main()
{
std::vector<int> v(10000, 1);
std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';
}
上面的代码来自 cpp reference,运行结果:The sum is 10000
。
std::this_thread
C++11专门提供了一个命名空间std::this_thread来表示当前线程。std::this_thread提供了几个方法可以对线程做一定的控制:
- get_id(),获取线程id
- yield(), 释放执行权
- sleep_for(),使线程沉睡一定时间
- …
下面是一个具体例子:
#include <iostream>
#include <future>
#include <thread>
using namespace std;
int f(promise<int> my_promise) {
string hi = "hello, world!";
my_promise.set_value(hi.size());
this_thread::sleep_for(0.1s);
cout << hi << endl;
}
int main() {
promise<int> f_promise;
auto result = f_promise.get_future();
thread f_thread(f, move(f_promise));
cout << result.get() << endl;
f_thread.join();
return 0;
}
程序退出相关的函数
由于增加了线程,如果优雅得退出一个程序变得更加复杂,因为当主线程决定要退出的时候,它并不知道其他线程正在干什么。
看看下面这些程序退出相关的函数:
- abort(),异常中止程序,不承诺清理资源,不调用atexit()注册的清理函数。实际上程序会接收到信号SIGABRT,并退出。
- terminate(), 程序中抛出异常,但是没有得到处理,于是调用这个函数来终止程序,实际的实现会调用abort()
- exit(),正常退出整个程序(包括所有线程),清理所有资源,并调用atexit()注册的清理函数。对于C++程序而言,会调用静态对象的析构函数,并处理好IO缓冲。
- quick_exit(), 正常退出整个程序,会处理好IO缓冲,但是不会调用静态对象的析构函数,并且会调用at_quick_exit()注册的清理函数
- _exit()/_Exit(),正常退出程序,但是不调用atexit()注册的清理函数,不清理任何资源
总结下问题:如果想使用exit()来从多线程函数中退出,必须等所有的线程都执行完。如果不进行这个同步的话,exit()执行的时候会对析构静态对象,而哪些在调用exit()之后才运行结束的线程也会析构静态对象,会导致对静态对象的二次析构,从而导致未定义的结果。另外一个选项是直接调用_exit()/_Exit()(这两等价)来直接终止程序,但是这样做完全不会清理任何资源,可能会导致重要的资源没有被释放。所以C++11中引入了quick_exit()函数,不对静态对象进行析构,但是可以通过at_quick_exit()注册清理函数,用来清理和释放重要的资源。
参考链接
- c++11faq#std-threads
- Why is there no std::future::then in C++17?
- C++17: I See a Monad in Your Future!
- What is the difference between packaged_task and async
- exit, _Exit, _exit
- What is the difference between std::quick_exit and std::abort and why was std::quick_exit needed?
- What is the difference between std::quick_exit and std::abort and why was std::quick_exit needed?
- C++11/C++14 Thread 1. Creating Threads
- Simpler Multithreading in C++0x
系列教程
- C++11 Multithreading – Part 8: std::future , std::promise and Returning values from Thread
- ultithreading in C++0x part 8: Futures, Promises and Asynchronous Function Calls
- Multithreading with C++17 and C++20
书籍