C++ std::thread 必须要熟悉的几个知识点
共 5414字,需浏览 11分钟
·
2021-07-12 17:13
std::thread
C++在 11 标准中增加了对线程(threads)的支持,看以下例子:
#include <thread>
#include <iostream>
using namespace std;
void f() {
cout << "hello, world!" << endl;
}
int main() {thread t(f);
t.join();
return 0;
}
使用 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()注册清理函数,用来清理和释放重要的资源。
来源:https://marvinsblog.net/post/2018-11-12-c-11-threads/
推荐:
Android FFmpeg 实现带滤镜的微信小视频录制功能
全网最全的 Android 音视频和 OpenGL ES 干货,都在这了
FFmpeg + Android AudioRecorder 音频录制编码