在C中已经有一个叫做pthread的东西来进行多线程编程,但是并不好用 (如果你认为句柄、回调式编程很实用,那请当我没说),所以c++11标准库中出现了一个叫作std::thread的东西。
C++中有多种可调用对象,他们可以作为参数传给std::bind()
,std::thread()
, std::async()
,std::call_once()
等。
std::thread 的实现背后是基于 pthread 的。
cmake
CMake中使用pthread的坑与解决方案_cmake 链接pthread-CSDN博客
cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 17)
project(cpptest LANGUAGES CXX)
add_executable(cpptest main.cpp)
# 重点是这一步
target_link_libraries(cpptest PUBLIC pthread)
makefile
-lpthread
excute=out
obj=test.cpp
$(excute): $(obj)
gcc $(obj) -lpthread -lstdc++ -o $(excute)
clean:
rm $(excute)
C++11 开始,为多线程提供了语言级别的支持。用 std::thread 这个类来表示线程。
何时执行与回收?
执行
线程是在thread对象被定义的时候开始执行的。
当那个线程启动时,就会执行这个 lambda 里的内容。
回收
作为一个 C++ 类,std::thread 同样遵循 RAII 思想和三五法则。
因为管理着资源,所以
当 std::thread对象
所在的函数退出时,就会调用 std::thread
的解构函数,这会销毁线程。
可能导致 std::thread
对象的函数还没开始执行,线程就被销毁了。
void myfunc() {
std::thread t1([&] {
download("hello.zip");
});
}
void main() {
myfunc();
}
std::thread
构造函数的参数可以是任意 lambda 表达式。
// 默认构造函数
// 创建一个线程,什么也不做
thread() noexcept;
// 初始化构造函数
// 创建一个线程,以args为参数,执行fn函数(注意,开始执行)
template <class Fn, class… Args>
explicit thread(Fn&& fn, Args&&… args);
// 移动构造函数
// 构造一个与x相同的对象,会破坏x对象
thread(thread&& x) noexcept;
// 如果对象是joinable的,那么会调用std::terminate()结果程序
thread& operator=(thread &&rhs);
// 析构函数 析构对象
~thread();
没有执行join或detach的线程在程序结束时会引发异常
调用join函数(阻塞)
分离的线程(执行过detach的线程)
让主线程不要急着退出,等子线程也结束了再退出
// 等待线程结束并清理资源(会阻塞)
void join();
// 返回线程是否可以执行join函数
bool joinable();
std::thread t1([&] {
download("hello.zip");
});
t1.join();
detach
调用成员函数 detach() 分离该线程
不过这样还是会在进程退出时候自动退出
// 将线程与调用其的线程分离,彼此独立执行(此函数必须在线程创建时立即调用,且调用此函数会使其不能被join)
void detach();
void myfunc() {
std::thread t1([&] {
download("hello.zip");
});
t1.detach();
}
void main() {
myfunc();
}
detach 的问题是进程退出时,进程不会等待所有子线程执行完毕。
另一种解法是把 t1 对象移动到一个全局变量去,再join它们,从而延长其生命周期到 myfunc 函数体外。
从中取出thread,手动join
std::vector<std::thread> pool;
void myfunc() {
std::thread t1([&] {
download("hello.zip");
});
pool.push_back(std::move(t1)); // 移交thread的控制权(所有权)给全局变量,延长生命周期
}
void main() {
myfunc();
for (auto &t: pool) t.join(); // 手动join
}
在 main 里面手动 join 全部线程还是有点麻烦,
我们可以自定义一个类 ThreadPool,并用他创建一个全局变量,其解构函数会在 main 退出后自动调用。
class ThreadPool {
std::vector<std::thread> m_pool;
public:
void push_back(std::thread thr) {
m_pool.push_back(std::move(thr));
}
~ThreadPool() { // 即将离开作用域(进程要结束时),自动调用
for (auto &t: m_pool) t.join(); // 等待线程池中的线程全部结束
}
};
ThreadPool tpool;
void myfunc() {
std::thread t1([&] {
download("hello.zip");
});
tpool.push_back(std::move(t1)); // 移交thread的控制权(所有权)给全局变量,延长生命周期
}
int main() {
myfunc();
return 0;
}
线程控制自己的方法,在
头文件中,不仅有std::thread这个类,而且还有一个std::this_thread命名空间,它可以很方便地让线程对自己进行控制。
std::this_thread
是个命名空间,所以你可以使用using namespace std::this_thread;
这样的语句来展开这个命名空间,不过我不建议这么做。
// 获取当前线程id
std::thread::id get_id() noexcept
// 等待sleep_duration(sleep_duration是一段时间)
template<class Rep, class Period>
void sleep_for( const std::chrono::duration<Rep, Period>& sleep_duration )
// 暂时放弃线程的执行,将主动权交给其他线程(主动权还会回来)
void yield() noexcept
//获取线程id
std::thread::id get_id();
#include
#include
#include
using namespace std;
atomic_bool ready = 0;
// uintmax_t ==> unsigned long long
void sleep(uintmax_t ms) {
this_thread::sleep_for(chrono::milliseconds(ms));
}
void count() {
while (!ready) this_thread::yield();
for (int i = 0; i <= 20'0000'0000; i++);
cout << "Thread " << this_thread::get_id() << " finished!" << endl;
return;
}
int main() {
thread th[10];
for (int i = 0; i < 10; i++)
th[i] = thread(::count);
sleep(5000);
ready = true;
cout << "Start!" << endl;
for (int i = 0; i < 10; i++)
th[i].join();
return 0;
}
std::promise
实际上是std::future
的一个包装,一般情况下:
使用std::future
获取async创建线程的返回值。
使用std::promise
获取thread创建线程的返回值。
如果不想让 std::async
帮你自动创建线程,想要手动创建线程,可以直接用 std::promise
。
如果使用thread以引用传递返回值的话,就必须要改变future的值,std::future的值不能改变,那么如何利用引用传递返回值?
答:改不了 我新建一个
可以通过promise来创建一个拥有特定值的future。
返回的是新创建的future对象,没有改变任何已有future的值
在外面看起来就是,future的值不能改变,promise的值可以改变。
如何获得thread的返回值?过程是什么样的呢?
在主线程
声明一个 std::promise
变量。
意味着这个值,虽然现在还没有,但是以后会有的,会有子线程把它填上的。
在子线程中,
设置在上一步承诺过会被填进去的值。
调用 std::promise
的 set_value()
方法
在主线程里,
用 get_future() 获取其 std::future 对象,
进一步 std::future::get()
可以等待并获取线程返回值。
如果线程没有执行完,这里就阻塞。对应的场景就是,哎,你不是说过以后会有返回值的吗?你不能烂尾,就阻塞在这里,弄完了返回值再走。
// 默认构造函数 构造一个空的promise对象
promise()
// 带参构造函数
// 与默认构造函数相同,但使用特定的内存分配器alloc构造对象
template <class Alloc>
promise(allocator_arg_t aa, const Alloc& alloc)
promise (const promise&) = delete // 复制构造函数 (已删除)
promise (promise&& x) noexcept // 移动构造函数 构造一个与x相同的对象并破坏x
~promise() // 析构函数 析构对象
set_value
std::promise
的 set_value() 不接受参数,仅仅作为同步用,不传递任何实际的值。
// set_value函数
// - 设置promise的值
// - 将共享状态设为ready(将future_status设为ready)
// void特化:只将共享状态设为ready
// 一般
void set_value (const T& val)
void set_value (T&& val)
// 当类型为引用
void promise<R&>::set_value (R& val)
// 当类型为void
void promise::set_value (void)
get_future
// 构造一个future对象,
// 其值与promise相同,status也与promise相同
future get_future()
声明保存 int 的promise
void download(std::string file, promise<int> &pret) {
std::this_thread::sleep_for(std::chrono::milliseconds(400));
pret.set_value(404); // 向promise中写入值,创建future
}
int main() {
//声明一个std::promise对象pret,其保存的值类型为int
std::promise<int> pret;
std::thread t1([&] {
auto ret = download("hello.zip", pret);
});
std::future<int> fret = pret.get_future(); // 从promise中取出future
int ret = fret.get(); // 从future中取出返回值
std::cout << "Download result: " << ret << std::endl;
t1.join();
return 0;
}
声明保存 string 的promise
void download(std::string file, promise<string> &pret) {
std::this_thread::sleep_for(std::chrono::milliseconds(400));
pret.set_value("for promise"); // 向promise中写入值,创建future
// return 1;
}
int main() {
//声明一个std::promise对象pret,其保存的值类型为int
std::promise<string> pret;
std::thread t1([&] {
download("hello.zip", pret);
});
std::future<string> fret = pret.get_future(); // 从promise中取出future
string ret = fret.get(); // 从future中取出返回值
std::cout << "Download result: " << ret << std::endl;
t1.join();
return 0;
}
// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include
#include
#include // std::promise std::future
using namespace std;
template<class ... Args> decltype(auto) sum(Args&&... args) {
return (0 + ... + args);
}
template<class ... Args> void sum_thread(promise<long long> &val, Args&&... args) {
val.set_value(sum(args...));
}
int main() {
promise<long long> sum_value;
thread get_sum(sum_thread<int, int, int>, ref(sum_value), 1, 10, 100);
cout << sum_value.get_future().get() << endl;
get_sum.join();
return 0;
}
不同于thread,async是一个模板函数,所以没有成员函数。
出现原因:
std::async 有两种构造函数
// 异步/同步交给操作系统选择
template <class Fn, class… Args>
future<typename result_of<Fn(Args…)>::type>
async (Fn&& fn, Args&&… args);
// 异步/同步手动指定选择
template <class Fn, class… Args>
future<typename result_of<Fn(Args…)>::type>
async (launch policy, Fn&& fn, Args&&… args);
launch::deferred
launch::async
视操作系统而定。
// 不显式指定,交给操作系统
std::future<int> fret = std::async([&] {
return download("hello.zip");
});
int ret = fret.get();
// 不指定,显式的交给操作系统
std::future<int> fret = std::async(std::launch::async | std::launch::defereed, [&] {
return download("hello.zip");
});
int ret = fret.get();
异步或同步,根据policy参数而定
std::launch
有2个枚举值和1个特殊值:
template <class Fn, class… Args>
future<typename result_of<Fn(Args…)>::type>
async (launch policy, Fn&& fn, Args&&… args);
std::launch::deferred
也就是 0x2
同步启动
创建 std::async
时,不会创建一个线程来执行,
在调用future::get
、future::wait
时,才启动函数体
推迟 lambda 函数体内的运算 到 future 的 get()或 wait() 被调用时
线程的执行仍在主线程中,
这只是函数式编程范式意义上的异步,而不涉及到真正的多线程。
可以用这个实现惰性求值(lazy evaluation)之类。
std::future<int> fret = std::async(std::launch::deferred, [&] {
return download("hello.zip");
});
// 此时才开始启动
int ret = fret.get();
std::launch::async
也就是 0x1
// 此时就已经启动
std::future<int> fret = std::async(std::launch::async, [&] {
return download("hello.zip");
});
int ret = fret.get();
std::future的作用
检测 async 线程是否已结束
获取 async 返回值
std::async
里 lambda 的返回类型可以为 void, 这时 future 对象的类型为 std::future
。
作为句柄,让async 阻塞等待/限时等待
所以对于返回值是void的线程来说,future也同样重要。
int main() {
// 注:这里不能只写函数名sum,必须带模板参数
future<int> val = async(launch::async, sum<int, int, int>, 1, 10, 100);
// future::get() 阻塞等待线程结束并获得返回值
cout << val.get() << endl;
return 0;
}
future() noexcept // 默认构造函数
// 构造一个空的、无效的future对象,但可以移动分配到另一个future对象
future(const future&) = delete // 复制构造函数 (已删除)
future (future&& x) noexcept // 移动构造函数 构造一个与x相同的对象并破坏x
~future() // 析构函数
需要调用 future 的方法,等待线程执行完毕。
调用次数:只能调用一次
行为:
阻塞等待线程结束并获取返回值
如果还没完成,阻塞等待,
只要线程没有执行完,会无限等下去。
获取返回值。
若future类型为void,则future::get()
与future::wait()
相同
如果是同步launch::deferred
的async,启动的asycn函数
T get() // 一般情况
R& future<R&>::get() // 类型为引用
void:void future::get() // 当类型为void,则与future::wait()相同
std::future<int> fret = std::async([&] {
return download("hello.zip");
});
// 等待线程执行结束
int ret = fret.get();
调用次数:只能调用一次
行为:
阻塞等待线程结束
如果还没完成,阻塞等待,只要线程没有执行完,会无限等下去。
不会获取返回值。
但是可以在future::wait()
结束后,再次通过 future::get()
获取返回值
如果是同步launch::deferred
的async,启动的asycn函数
std::future<int> fret = std::async([&] {
return download("hello.zip");
});
// 等待线程执行结束,不获取返回值
fret.wait();
// 虽然已经结束,但还是可以获取返回值
int ret = fret.get();
调用次数:无限制
行为:
阻塞等待 线程结束/限定时间到
如果还没完成,阻塞等待
返回值
类型: std::future_status
表示等待是否成功。
返回 std::future_status::timeout
线程在指定的时间内没有执行完毕,放弃等待
返回 std::future_status::ready
线程在指定的时间内执行完毕,等待成功
如果是同步launch::deferred
的async,启动的asycn函数
template <class Rep, class Period>
future_status wait_for(const chrono::duration<Rep,Period>& rel_time) const;
std::future<int> fret = std::async([&] {
return download("hello.zip");
});
while (true) {
// 循环多次等待
auto stat = fret.wait_for(std::chrono::milliseconds(1000));
if (stat == std::future_status::ready) {
std::cout << "Future is ready!!" << std::endl;
break;
} else {
std::cout << "Future not ready!!" << std::endl;
}
}
// 虽然已经结束,但还是可以获取返回值
int ret = fret.get();
wait_until()
std::future<int> fret = std::async([&] {
return download("hello.zip");
});
while (true) {
// 循环多次等待
auto stat = fret.wait_for(std::chrono::milliseconds(1000));
if (stat == std::future_status::ready) {
std::cout << "Future is ready!!" << std::endl;
break;
} else {
std::cout << "Future not ready!!" << std::endl;
}
}
// 虽然已经结束,但还是可以获取返回值
int ret = fret.get();
future 为了三五法则,删除了拷贝构造/赋值函数。如果需要浅拷贝,实现共享同一个 future 对象,可以用 std::shared_future。
void download(std::string file) {
std::this_thread::sleep_for(std::chrono::milliseconds(400));
std::cout << "Download complete: " << file << std::endl;
}
int main() {
std::shared_future<void> fret = std::async([&] {
download("hello.zip");
});
auto fret2 = fret;
auto fret3 = fret;
fret3.wait();
std::cout << "Download completed" << std::endl;
return 0;
}
std::packaged_task
,绑定一个可调用对象,在未来期望的时候执行。
更方便的,天然异步的函数对象。
将可调对象传递给关联的std::future
对象
包装std::promise
中的可调对象T= std::function
可调用对象的执行、返回值获取 需要透过 future
。
std::packaged_task
的返回类型是void
int download02() {
cout << "start download " << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(400));
cout << "set promise value " << endl;
return 2;
}
int main()
{
// 给packaged_task绑定函数对象
std::packaged_task<int()> t(download02);
// 指定函数对象的future返回值
std::future<int> fu2 = t.get_future();
// 执行packaged_task绑定的函数对象
t();
// 先执行之后,才可以获取返回值
int result = fu2.get();
std::cout << result << std::endl;
return 0;
}
考虑这样一个场景:多个线程共享一个任务队列
这里简化一下这个场景。主线程产生任务,一个子线程t1
执行。
std::invoke_result_t
std::packaged_task
C++20 引入了 std::jthread 类,
和 std::thread 不同在于:
native_handler
jthread 自动join
在多线程的环境下, 某些可执行对象只需要执行一次。std::call_once 应运而生。
原理:判断全局变量标识符once_flag。如果这个once_flag
template< class Function, class... Args >
void call_once (std::once_flag& flag, Function&& f, Args&& args... );
// 参数解析:
// std::once_flag flag 判断是个需要执行。若执行,执行后关闭。
// f 和 args... 需要被调用的函数以及函数f的传参
// lambda 常用 [this]
// 抛出异常 std::system_error if any condition prevents calls to call_once from executing as specified any exception thrown by f
// 初始化资源(节省数组判空)
// 单例模式
#include
#include
#include
#include
std::once_flag flag1;
class PySingleton {
public:
static PySingleton* mInstance;
static PySingleton* get_instance() {
std::call_once(flag1, [] (int id) {
mInstance = new PySingleton(id);
}, 1);
return mInstance;
}
private:
PySingleton(int id) {
std::cout << " do init, id = " << id << " \n";
}
};
PySingleton* PySingleton::mInstance = nullptr; // 必须在这里 为static mInstance,编译期执行
void test() {
PySingleton* py = PySingleton::get_instance();
}
int main()
{
std::thread mThread(test);
mThread.join();
return 0;
}
#include
#include
#include
#include
std::once_flag flag1;
void do_print1() {
std::cout << "do_print1 example: called once\n";
}
void do_print2() {
std::cout << "do_print2 example: called once\n";
}
void simple_do_once()
{
std::function<void()> mFunc1 = do_print1;
std::function<void()> mFunc2 = do_print2;
std::call_once(flag1, mFunc1); // 仅仅执行了一次这个
std::call_once(flag1, mFunc2);
}
int main()
{
std::thread st1(simple_do_once);
std::thread st2(simple_do_once); // 什么都没做
st1.join();
st2.join();
return 0;
}