做公众号必了解的网站,磁力兔子搜索引擎,黑蒜东莞网站建设,连云港 网站设计目录 二、day2
1. 线程管控
1.1 归属权转移
1.2 joining_thread
1.2.1 如何使用 joining_thread
1.3 std::jthread
1.3.1 零开销原则
1.3.2 线程停止
1.4 容器管理线程对象
1.4.1 使用容器
1.4.2 如何选择线程运行数量
1.5 线程id 二、day2
今天学习如何管理线程包括
1线程的归属权如何进行转移
2joining_thread
3jthread
4如何使用容器对线程进行管理并简单举了一个关于多线程并发的例子
5如何辨识线程通过线程id
参考
恋恋风辰官方博客llfc.club/category?catid225RaiVNI8pFDD5L4m807g7ZwmF#!aid/2Tuk4RfvfBC788LlqnQrWiPiEGWhttps://link.zhihu.com/?targethttps%3A//llfc.club/category%3Fcatid%3D225RaiVNI8pFDD5L4m807g7ZwmF%23%21aid/2Tuk4RfvfBC788LlqnQrWiPiEGW
https://www.bilibili.com/video/BV1v8411R7hD?vd_sourcecb95e3058c2624d2641da6f4eeb7e3a1www.bilibili.com/video/BV1v8411R7hD?vd_sourcecb95e3058c2624d2641da6f4eeb7e3a1https://link.zhihu.com/?targethttps%3A//www.bilibili.com/video/BV1v8411R7hD%3Fvd_source%3Dcb95e3058c2624d2641da6f4eeb7e3a1
ModernCpp-ConcurrentProgramming-Tutorial/md/02使用线程.md at main · Mq-b/ModernCpp-ConcurrentProgramming-Tutorialgithub.com/Mq-b/ModernCpp-ConcurrentProgramming-Tutorial/blob/main/md/02%E4%BD%BF%E7%94%A8%E7%BA%BF%E7%A8%8B.md编辑https://link.zhihu.com/?targethttps%3A//github.com/Mq-b/ModernCpp-ConcurrentProgramming-Tutorial/blob/main/md/02%25E4%25BD%25BF%25E7%2594%25A8%25E7%25BA%25BF%25E7%25A8%258B.md 1. 线程管控
在上一节中我们详细解析了 thread 的源码知道了 thread 只有一个私有数据成员_Thr 类型是一个结构体包含两个数据成员_Hnd 和 _Id前者指向线程的句柄后者保有线程的 ID。并且thread禁用了拷贝构造函数和赋值运算符所以只能通过移动赋值运算符、移动构造函数和局部变量返回的方式将线程变量管理的线程所有权转移给其他变量管理。
// 移动构造函数
thread(thread _Other) noexcept : _Thr(_STD exchange(_Other._Thr, {})) {}
// 移动赋值运算符
thread operator(thread _Other) noexcept {if (joinable()) {_STD terminate(); // per N4950 [thread.thread.assign]/1}_Thr _STD exchange(_Other._Thr, {});return *this;
}
// 将拷贝构造函数和拷贝赋值运算符禁止
thread(const thread) delete;
thread operator(const thread) delete;可以参考上一节的文章
爱吃土豆并发编程1——线程1 赞同 · 0 评论文章https://zhuanlan.zhihu.com/p/3601853865
1.1 归属权转移
线程可以通过detach在后台运行或者让开辟这个线程的父线程等待该线程完成。但每个线程都应该有其归属权也就是归属给某个变量管理
void some_function() {
}
std::thread t1(some_function);在该段代码中t1管理一个运行 some_function 函数的线程。
我们可以通过下面几种方式将线程归属权进行转移
void some_function() {while (true) {std::this_thread::sleep_for(std::chrono::seconds(1));}
}
void some_other_function() {while (true) {std::this_thread::sleep_for(std::chrono::seconds(1));}
}
//t1 绑定some_function
std::thread t1(some_function);
//2 转移t1管理的线程给t2转移后t1无效
std::thread t2 std::move(t1);
//3 t1 可继续绑定其他线程,执行some_other_function
t1 std::thread(some_other_function);
//4 创建一个线程变量t3
std::thread t3;
//5 转移t2管理的线程给t3
t3 std::move(t2);
//6 转移t3管理的线程给t4
std::thread t4(std::move(t3));
//7 转移t4管理的线程给t1
t1 std::move(t4);
std::this_thread::sleep_for(std::chrono::seconds(2000));首先创建一个线程运行 some_function函数并交给线程变量 t1 进行管理我们可以通过移动构造函数或者移动赋值运算符进行归属权转移。我们这里使用移动赋值运算符将t1的线程归属权转移给t2此后t2正式管理t1之前管理的线程而t1无效
std::thread t1(some_function);
std::thread t2 std::move(t1);
t1 std::thread(some_other_function);t1 无效后可继续绑定其他线程注意 std::thread(some_other_function)返回的是一个thread类型的右值引用然后通过移动赋值运算符将线程管理权转移给t1赋值运算符被delete所以只能右值赋值
//6 转移t3管理的线程给t4
std::thread t4(std::move(t3));我们也可以使用移动构造函数将t3的所有权转移给t4.
但是我们不能将线程管理权转移给一个已经有另一个线程管理权的线程变量比如
t1 std::move(t4);该段代码会造成程序崩溃。所以不能将一个线程的管理权交给一个已经绑定线程的变量否则会触发线程的terminate函数引发崩溃。
此外我们也可以通过返回局部变量的方式转移所属权
// 法1
std::thread f() {return std::thread(some_function);
}
// 法2
void param_function(int a) {while (true) {std::this_thread::sleep_for(std::chrono::seconds(1));}
}
std::thread g() {std::thread t(param_function, 43);return t;
}int main() {// 调用 f() 并获取返回的 std::thread 对象std::thread t1 f();// 等待线程结束if (t1.joinable()) {t1.join(); // 确保线程执行完毕}// 调用 g() 并获取返回的 std::thread 对象std::thread t2 g();// 等待线程结束if (t2.joinable()) {t2.join(); // 确保线程执行完毕}return 0;
}我们可以在函数内返回创建好的thread对象右值因为是临时对象C 在返回局部变量时会优先寻找这个类的拷贝构造函数如果没有就会使用这个类的移动构造函数
返回的类型不能是引用临时对象会在函数结束时被销毁而返回的引用会指向一个已经被销毁的对象。 std::thread f() {return std::thread(some_function);
}上段代码是错误的返回局部变量时不能返回引用。
1.2 joining_thread
在上节中我们学习了线程守卫RAII技术即主线程出现异常时希望子线程能够运行结束后才退出主程序。其实 joining_thread 就是一个自带RAII技术的 thread类它和 std::thread 的区别就是析构函数会自动 join 。
joining_thread 是 C17标准的备选提案但是并没有被引进直至它改名为 std::jthread 后进入了C20标准的议程现已被正式纳入C20标准。 boost库中有 joining_thread 类的实现。 我们存储一个 std::thread 作为底层数据成员稍微注意一下构造函数、赋值运算符和析构函数的实现即可其他实现和thread的相似。
class joining_thread {std::thread t;
public:// 构造函数1joining_thread()noexcept default;// 构造函数2templatetypename Callable, typename... Argsexplicit joining_thread(Callable func, Args...args) :t{ std::forwardCallable(func), std::forwardArgs(args)... } {}// 构造函数3explicit joining_thread(std::thread t_)noexcept : t{ std::move(t_) } {}// 移动构造函数.4joining_thread(joining_thread other)noexcept : t{ std::move(other.t) } {}// 移动赋值运算符5joining_thread operator(std::thread other)noexcept {// 如果当前线程_t有任务运行且未调用过join或detachif (joinable()) { // 如果当前有活跃线程那就先执行完join();}// 执行完_t线程的任务后转移other管理权给_tt std::move(other);return *this;}// 构造函数6joining_thread operator(joining_thread other) noexcept{//如果当前线程可汇合则汇合等待线程完成再赋值if (joinable()) {join();}_t std::move(other._t);return *this;}// 析构函数~joining_thread() {if (joinable()) {join();}}void swap(joining_thread other)noexcept {t.swap(other.t);}std::thread::id get_id()const noexcept {return t.get_id();}bool joinable()const noexcept {return t.joinable();}void join() {t.join();}void detach() {t.detach();}std::thread data()noexcept {return t;}const std::thread data()const noexcept {return t;}
};注意如果使用移动赋值运算符和赋值运算符时joining_thread 的私有成员 _t 有任务在运行那么必须等待_t的任务运行结束后才能将 other 的线程所属权转移给_t否则会造成崩溃。因为不能将一个线程的管理权交给一个已经绑定线程的变量否则会触发线程的terminate函数引发崩溃。 // 如果当前线程_t有任务运行且未调用过join或detachif (joinable()) { // 如果当前有活跃线程那就先执行完join();}// 执行完_t线程的任务后转移other管理权给_tt std::move(other);return *this;1.2.1 如何使用 joining_thread
void use_jointhread() {//1 根据线程构造函数构造joiningthreadjoining_thread j1([](int maxindex) {for (int i 0; i maxindex; i) {std::cout in thread id std::this_thread::get_id() cur index is i std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}}, 10);//2 根据thread构造joiningthreadjoining_thread j2(std::thread([](int maxindex) {for (int i 0; i maxindex; i) {std::cout in thread id std::this_thread::get_id() cur index is i std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}}, 10));//3 根据thread构造j3joining_thread j3(std::thread([](int maxindex) {for (int i 0; i maxindex; i) {std::cout in thread id std::this_thread::get_id() cur index is i std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}}, 10));//4 把j3赋值给j1joining_thread内部会等待j1汇合结束后//再将j3赋值给j1j1 std::move(j3);
}线程变量 j1 通过构造函数2构造 joining_thread j1([](int maxindex) {for (int i 0; i maxindex; i) {std::cout in thread id std::this_thread::get_id() cur index is i std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}}, 10);templatetypename Callable, typename... Argsexplicit joining_thread(Callable func, Args...args) :t{ std::forwardCallable(func), std::forwardArgs(args)... } {}线程变量 j2 通过构造函数3构造
首先通过std::thread 构造一个thread对象然后将thread传入joining_thread 的构造函数中 joining_thread j2(std::thread([](int maxindex) {for (int i 0; i maxindex; i) {std::cout in thread id std::this_thread::get_id() cur index is i std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}}, 10));explicit joining_thread(std::thread t_)noexcept : t{ std::move(t_) } {}同理j3的构造方式和j2相同如果将j3以移动语义赋值给j1那么j1内部首先会等待j1管理的线程任务结束后才会将j3的线程所属权转移给j1。使用了移动赋值运算符。 j1 std::move(j3);// 移动赋值运算符5joining_thread operator(std::thread other)noexcept {// 如果当前线程_t有任务运行且未调用过join或detachif (joinable()) { // 如果当前有活跃线程那就先执行完join();}// 执行完_t线程的任务后转移other管理权给_tt std::move(other);return *this;}1.3 std::jthread
std::jthread 相比于 C11 引入的 std::thread只是多了两个功能
RAII 管理在析构时自动调用 join()。线程停止功能线程的取消/停止。
RAII技术已经说过很多次了这里就不在叙述可以参考前面的文章。这里主要说一下什么是线程停止功能。
1.3.1 零开销原则 为什么 C20 不直接为std::thread增加这两个功能而是创造一个新的线程类型呢 这就是 C 的设计哲学零开销原则你不需要为你没有用到的特性付出额外的开销。
std::jthread 的通常实现就是单纯的保有 std::thread std::stop_source 这两个数据成员joining_thread只有一个成员std::thread
thread _Impl;
stop_source _Ssource;stop_source 通常占 8 字节先前 std::thread 源码解析详细聊过其不同标准库对其保有的成员不同(内存对齐)简单来说也就是 64 位环境大小为 16 或者 8。也就是 sizeof(std::jthread) 的值相比 std::thread 会多 8 为 24 或 16。
引入 std::jthread 符合零开销原则它通过创建新的类型提供了更多的功能而没有影响到原来 std::thread 的性能和内存占用。
1.3.2 线程停止
首先要明确C 的 std::jthread 提供的线程停止功能并不同于常见的 POSIX 函数 pthread_cancel。pthread_cancel 是一种发送取消请求的函数但并不是强制性的线程终止方式。目标线程的可取消性状态和类型决定了取消何时生效。当取消被执行时进行清理和终止线程。
std::jthread 所谓的线程停止只是一种基于用户代码的控制机制而不是一种与操作系统系统有关系的线程终止。使用 std::stop_source 和std::stop_token 提供了一种优雅地请求线程停止的方式但实际上停止的决定和实现都由用户代码来完成。如下
using namespace std::literals::chrono_literals;void f(std::stop_token stop_token, int value){while (!stop_token.stop_requested()){ // 检查是否已经收到停止请求std::cout value std::flush;std::this_thread::sleep_for(200ms);}std::cout std::endl;
}int main(){std::jthread thread{ f, 1 }; // 打印 1..15 大约 3 秒std::this_thread::sleep_for(3s);// jthread 的析构函数调用 request_stop() 和 join()。
}该段代码主要用于创建一个可以响应停止请求的线程。
当 std::jthread 对象超出作用域时它会自动调用 request_stop() 请求停止线程并在销毁时调用 join() 等待线程结束。std::stop_token 允许线程检查是否接收到停止请求。在函数 f 中循环体检查 stop_token.stop_requested()如果返回 false则继续执行否则退出循环。在每次循环中打印当前值并将其递增然后线程休眠 200 毫秒。这样每个数字的打印之间有一定的间隔。在 main 函数中主线程休眠 3 秒。这段时间内f 函数将打印数字大约会打印 15 个数字因为 3 秒内会输出 1 到 15。主线程结束后jthread 会自动请求停止并等待 f 函数完成。 std::jthread 提供了三个成员函数进行所谓的线程停止
get_stop_source返回与 jthread 对象关联的 std::stop_source允许从外部请求线程停止。get_stop_token返回与 jthread 对象停止状态关联的 std::stop_token允许检查是否有停止请求。request_stop请求线程停止。
上面那段代码中这三个函数并没有被显式调用不过在 jthread 的析构函数中会调用 request_stop 请求线程停止
void _Try_cancel_and_join() noexcept {if (_Impl.joinable()) {_Ssource.request_stop();_Impl.join();}
}
~jthread() {_Try_cancel_and_join();
}至于 std::jthread thread{ f, 1 } 函数 f 的 std::stop_token 的形参是谁传递的其实就是线程对象自己调用get_token()传递的 源码一眼便可发现
template class _Fn, class... _Args, enable_if_t!is_same_vremove_cvref_t_Fn, jthread, int 0
_NODISCARD_CTOR_JTHREAD explicit jthread(_Fn _Fx, _Args... _Ax) {if constexpr (is_invocable_vdecay_t_Fn, stop_token, decay_t_Args...) {_Impl._Start(_STD forward_Fn(_Fx), _Ssource.get_token(), _STD forward_Args(_Ax)...);} else {_Impl._Start(_STD forward_Fn(_Fx), _STD forward_Args(_Ax)...);}
}std::stop_source
这是一个可以发出停止请求的类型。当你调用 stop_source 的 request_stop() 方法时它会设置内部的停止状态为“已请求停止”。任何持有与这个 stop_source 关联的 std::stop_token 对象都能检查到这个停止请求。
std::stop_token
这是一个可以检查停止请求的类型。线程内部可以定期检查 stop_token 是否收到了停止请求。通过调用 stop_token.stop_requested()线程可以检测到停止状态是否已被设置为“已请求停止”。
1.4 容器管理线程对象
之前在网络编程封装两种线程池就是通过容器管理线程对象可以参考
爱吃土豆网络编程16——asio多线程模型IOServicePool5 赞同 · 0 评论文章https://zhuanlan.zhihu.com/p/890395457
爱吃土豆网络编程17——asio多线程模型IOThreadPool0 赞同 · 0 评论文章https://zhuanlan.zhihu.com/p/903104078
容器存储线程时比如 vector如果用 push_back 操作势必会调用std::thread这样会引发编译错误。
如果存在一个容器 std::vectorstd::thread当使用 push_back 将一个线程对象添加到这个容器时实际上会调用 std::thread 的拷贝构造函数但是std::thread 将拷贝构造函数delete编译器会报错。例如
std::vectorstd::thread threads;
threads.push_back(std::thread([] { /* thread code */ }));一般来说push_back会根据传入值的类型选择使用拷贝构造函数或者移动构造函数。而 std::thread 存在移动构造函数如果push_back不能使用thread的拷贝构造那为什么不使用移动构造函数呢 因为我们调用push_back 时传递的对象通常是一个左值即命名的变量。如果直接传递左值编译器会优先尝试调用拷贝构造函数如果不存在拷贝构造那么会直接报错而不会调用移动构造因为thread内部并不会将左值隐式转换为右值从而调用移动构造。
push_back 会尝试使用传递的对象调用拷贝构造函数左值传入。如果对象的拷贝构造函数不可用例如被删除则编译器会报错。如果对象的拷贝构造函数可用但移动构造函数也存在push_back 会选择合适的构造函数 如果传递的是一个左值会调用拷贝构造函数。如果传递的是一个右值会调用移动构造函数。
而emplace_back 允许我们直接在容器中构造对象而不需要先构造一个对象再传递给容器。这是通过完美转发实现的。当我们使用 emplace_back 时std::thread 对象会在 std::vector 的内部直接构造因此不会触发拷贝构造这使得它可以接受可调用对象如函数指针或 lambda 表达式和参数来创建线程。
std::vectorstd::thread threads;
threads.emplace_back(some_function); // 直接在 vector 中构造 std::thread 对象我们在之前网络编程实现IOServicePool或者IOThreadPool时初始化了多个线程存储在vector中, 采用的时emplace_back方式可以直接根据线程构造函数需要的参数构造这样就避免了调用thread的拷贝构造函数。
std::thread 对象在被创建时会关联到一个实际的线程。如果 std::thread 对象被移动到不是复制 std::vector 中原始的 std::thread 对象将失去对该线程的所有权即该对象变为无效并且只能在 std::vector 中的元素上调用 join() 或 detach() 来管理这个线程的生命周期。
1.4.1 使用容器
使用容器管理线程对象等待线程执行结束
void do_work(std::size_t id){std::cout id \n;
}int main(){std::vectorstd::threadthreads;for (std::size_t i 0; i 10; i){threads.emplace_back(do_work, i); // 产生线程}for(auto thread:threads){thread.join(); // 对每个线程对象调用 join()}
}线程对象代表了线程管理线程对象也就是管理线程这个 vector 对象管理 10 个线程保证他们的执行、退出。
使用我们这节实现的 joining_thread 则不需要最后的循环 join()joining_thread 对象在析构时会自动调用join()
int main(){std::vectorjoining_threadthreads;for (std::size_t i 0; i 10; i){threads.emplace_back(do_work, i);}
}注意到这两段代码的输出都是乱序的没有规律而且重复运行的结果还不一样这是正常现象。多线程执行就是如此无序且操作可能被打断。使用互斥量可以解决这些问题也就是下一章节的内容。
1.4.2 如何选择线程运行数量
在网络编程的学习中我们通过 std::thread::hardware_concurrency() 函数定义线程运行的数量该函数的作用就是返回当前设备CPU的核数以核数作为线程的运行数量希望CPU一个核中运行一个线程。
接下来举一个例子来说明多线程并行处理的优势。
templatetypename Iterator, typename T
struct accumulate_block
{void operator()(Iterator first, Iterator last, T result){result std::accumulate(first, last, result);}
};templatetypename Iterator, typename T
T parallel_accumulate(Iterator first, Iterator last, T init)
{unsigned long const length std::distance(first, last);if (!length)return init; //⇽-- - ①unsigned long const min_per_thread 25;unsigned long const max_threads (length min_per_thread - 1) / min_per_thread; //⇽-- - ②unsigned long const hardware_threads std::thread::hardware_concurrency();unsigned long const num_threads std::min(hardware_threads ! 0 ? hardware_threads : 2, max_threads); //⇽-- - ③unsigned long const block_size length / num_threads; //⇽-- - ④std::vectorT results(num_threads);std::vectorstd::thread threads(num_threads - 1); // ⇽-- - ⑤Iterator block_start first;for (unsigned long i 0; i (num_threads - 1); i){Iterator block_end block_start;std::advance(block_end, block_size); //⇽-- - ⑥threads[i] std::thread(//⇽-- - ⑦accumulate_blockIterator, T(),block_start, block_end, std::ref(results[i]));block_start block_end; //⇽-- - ⑧}accumulate_blockIterator, T()(block_start, last, results[num_threads - 1]); //⇽-- - ⑨for (auto entry : threads)entry.join(); //⇽-- - ⑩return std::accumulate(results.begin(), results.end(), init); //⇽-- - ⑪
}在上段代码中实现了一个函数模板 parallel_accumulate该函数通过多线程来加速对区间[first, last) 中元素的累加。下面是对这个函数的解释
templatetypename Iterator, typename T
struct accumulate_block
{void operator()(Iterator first, Iterator last, T result){result std::accumulate(first, last, result);}
};accumulate_block是一个仿函数参数为两个迭代器以及结果存储的对象。
templatetypename Iterator, typename T
T parallel_accumulate(Iterator first, Iterator last, T init)parallel_accumulate 是一个模板函数接受两个迭代器 first 和 last 以及一个初始值 init。返回值类型为 T表示累加的结果。接下来解析函数执行步骤
①计算长度
unsigned long const length std::distance(first, last);
if (!length)return init; //⇽-- - ①计算区间的长度如果长度为 0直接返回初始值确保区间内有元素。
②确定线程数
unsigned long const min_per_thread 25;
unsigned long const max_threads (length min_per_thread - 1) / min_per_thread; //⇽-- - ②
unsigned long const hardware_threads std::thread::hardware_concurrency();
unsigned long const num_threads std::min(hardware_threads ! 0 ? hardware_threads : 2, max_threads); //⇽-- - ③设定每个线程处理的最小元素数 min_per_thread然后计算最大线程数 max_threads获取当前硬件支持的最大线程数CPU核数并选择实际使用的线程数 num_threads理论最大线程数和硬件支持最大线程数的最小值
③计算区块大小
unsigned long const block_size length / num_threads; //⇽-- - ④
std::vectorT results(num_threads);
std::vectorstd::thread threads(num_threads - 1); // ⇽-- - ⑤计算每个线程应该处理的区块大小并创建结果存储的 results 向量和线程对象的向量 results 是一个大小为实际线程数num_threads的容器用于记录每个线程的求解值我们也使用vector对每个线程进行管理存储线程的容器threads的大小并不是num_threads 而是num_threads -1因为主线程也会帮忙计算如果主线程需要执行其他功能那么threads的大小需要设为num_threads
④启动线程
Iterator block_start first;
for (unsigned long i 0; i (num_threads - 1); i)
{Iterator block_end block_start;std::advance(block_end, block_size); //⇽-- - ⑥threads[i] std::thread(//⇽-- - ⑦accumulate_blockIterator, T(),block_start, block_end, std::ref(results[i]));block_start block_end; //⇽-- - ⑧
}在循环中将区间划分为多个区块为每个区块创建线程并开始执行 accumulate_block 函数每个线程都负责一个区块。注意这里启动的线程数不是 num_threads而是num_threads-1因为最后一个区块的长度可能不是block_size所以我们需要单独对这个区块进行处理。我们传入的参数是区间的范围以及需要存储在哪里
threads[i] std::thread(//⇽-- - ⑦accumulate_blockIterator, T(),block_start, block_end, std::ref(results[i]));std::advance用于将迭代器每次都向前移动block_size个大小block_size是每个线程处理的长度 std::vectorint vec {1, 2, 3, 4, 5};auto it vec.begin();// 向前移动 2 步std::advance(it, 2);std::cout *it std::endl; // 输出 3// 向后移动 1 步std::advance(it, -1);std::cout *it std::endl; // 输出 2⑤处理最后一个区块
accumulate_blockIterator, T()(block_start, last, results[num_threads - 1]); //⇽-- - ⑨对最后一个区块单独处理确保所有元素都被累加。
⑥等待线程结束
for (auto entry : threads)entry.join(); //⇽-- - ⑩等待所有线程完成
⑦返回结果
return std::accumulate(results.begin(), results.end(), init); //⇽-- - ⑪将各个线程的结果进行累加最终返回总和
最后我们调用这个函数进行测试
void use_parallel_acc() {std::vector int vec(10000);for (int i 0; i 10000; i) {vec.push_back(i);}int sum 0;sum parallel_accumulatestd::vectorint::iterator, int(vec.begin(), vec.end(), sum);std::cout sum is sum std::endl;
}输出结果为 1.5 线程id
还记得么thread唯一的私有成员变量是一个结构体该结构体中存储着线程的id我们可以根据线程id判断不同线程是否是同一个线程。
std::thread t([](){std::cout thread start std::endl;
});
t.get_id();通过 get_id() 函数我们可以获取指定线程变量的线程id。
但是如果我们想在线程的运行函数中区分线程或者判断哪些是主线程或者子线程可以通过这个方式
std::thread t([](){std::cout in thread id std::this_thread::get_id() std::endl;std::cout thread start std::endl;
});在传入thread的lambda函数中通过 std::this_thread::get_id() 可以获取当前线程的唯一标识符ID。
std::this_thread 是 C 标准库中的一个命名空间提供了一组与当前线程相关的功能。主要用于获取当前线程的信息和管理线程的执行。 文章转载自: http://www.morning.jbtlf.cn.gov.cn.jbtlf.cn http://www.morning.lsssx.cn.gov.cn.lsssx.cn http://www.morning.ndtmz.cn.gov.cn.ndtmz.cn http://www.morning.czxrg.cn.gov.cn.czxrg.cn http://www.morning.gmswp.cn.gov.cn.gmswp.cn http://www.morning.tnkwj.cn.gov.cn.tnkwj.cn http://www.morning.qhczg.cn.gov.cn.qhczg.cn http://www.morning.hmwjk.cn.gov.cn.hmwjk.cn http://www.morning.zcmpk.cn.gov.cn.zcmpk.cn http://www.morning.nzcys.cn.gov.cn.nzcys.cn http://www.morning.dtrcl.cn.gov.cn.dtrcl.cn http://www.morning.hpjpy.cn.gov.cn.hpjpy.cn http://www.morning.jkwwm.cn.gov.cn.jkwwm.cn http://www.morning.wrtxk.cn.gov.cn.wrtxk.cn http://www.morning.qzdxy.cn.gov.cn.qzdxy.cn http://www.morning.c-ae.cn.gov.cn.c-ae.cn http://www.morning.dycbp.cn.gov.cn.dycbp.cn http://www.morning.tpnxr.cn.gov.cn.tpnxr.cn http://www.morning.kkgbs.cn.gov.cn.kkgbs.cn http://www.morning.phjyb.cn.gov.cn.phjyb.cn http://www.morning.qtfss.cn.gov.cn.qtfss.cn http://www.morning.xykst.cn.gov.cn.xykst.cn http://www.morning.fpqq.cn.gov.cn.fpqq.cn http://www.morning.sqfnx.cn.gov.cn.sqfnx.cn http://www.morning.hxfrd.cn.gov.cn.hxfrd.cn http://www.morning.pgfkl.cn.gov.cn.pgfkl.cn http://www.morning.lhldx.cn.gov.cn.lhldx.cn http://www.morning.xhddb.cn.gov.cn.xhddb.cn http://www.morning.supera.com.cn.gov.cn.supera.com.cn http://www.morning.mnsts.cn.gov.cn.mnsts.cn http://www.morning.kqlrl.cn.gov.cn.kqlrl.cn http://www.morning.ylmxs.cn.gov.cn.ylmxs.cn http://www.morning.pxbky.cn.gov.cn.pxbky.cn http://www.morning.qmbpy.cn.gov.cn.qmbpy.cn http://www.morning.lgxzj.cn.gov.cn.lgxzj.cn http://www.morning.knqck.cn.gov.cn.knqck.cn http://www.morning.nqrlz.cn.gov.cn.nqrlz.cn http://www.morning.wrtw.cn.gov.cn.wrtw.cn http://www.morning.rkmsm.cn.gov.cn.rkmsm.cn http://www.morning.lmyq.cn.gov.cn.lmyq.cn http://www.morning.rqqmd.cn.gov.cn.rqqmd.cn http://www.morning.zrgsg.cn.gov.cn.zrgsg.cn http://www.morning.bfhfb.cn.gov.cn.bfhfb.cn http://www.morning.hnk25076he.cn.gov.cn.hnk25076he.cn http://www.morning.rwqk.cn.gov.cn.rwqk.cn http://www.morning.mzmqg.cn.gov.cn.mzmqg.cn http://www.morning.ghrlx.cn.gov.cn.ghrlx.cn http://www.morning.xqxlb.cn.gov.cn.xqxlb.cn http://www.morning.tqwcm.cn.gov.cn.tqwcm.cn http://www.morning.zwyuan.com.gov.cn.zwyuan.com http://www.morning.rdqzl.cn.gov.cn.rdqzl.cn http://www.morning.xrqkm.cn.gov.cn.xrqkm.cn http://www.morning.jyknk.cn.gov.cn.jyknk.cn http://www.morning.xswrb.cn.gov.cn.xswrb.cn http://www.morning.wnxqf.cn.gov.cn.wnxqf.cn http://www.morning.btgxf.cn.gov.cn.btgxf.cn http://www.morning.wnkbf.cn.gov.cn.wnkbf.cn http://www.morning.pwwjs.cn.gov.cn.pwwjs.cn http://www.morning.jmdpp.cn.gov.cn.jmdpp.cn http://www.morning.fxkgp.cn.gov.cn.fxkgp.cn http://www.morning.yltyz.cn.gov.cn.yltyz.cn http://www.morning.mjmtm.cn.gov.cn.mjmtm.cn http://www.morning.ygwyt.cn.gov.cn.ygwyt.cn http://www.morning.znqmh.cn.gov.cn.znqmh.cn http://www.morning.lpskm.cn.gov.cn.lpskm.cn http://www.morning.jbxmb.cn.gov.cn.jbxmb.cn http://www.morning.fmjzl.cn.gov.cn.fmjzl.cn http://www.morning.cwgfq.cn.gov.cn.cwgfq.cn http://www.morning.rlhh.cn.gov.cn.rlhh.cn http://www.morning.ksjmt.cn.gov.cn.ksjmt.cn http://www.morning.zwmjq.cn.gov.cn.zwmjq.cn http://www.morning.tfwsk.cn.gov.cn.tfwsk.cn http://www.morning.wzjhl.cn.gov.cn.wzjhl.cn http://www.morning.bnkcl.cn.gov.cn.bnkcl.cn http://www.morning.tkflb.cn.gov.cn.tkflb.cn http://www.morning.rrxnz.cn.gov.cn.rrxnz.cn http://www.morning.nqyfm.cn.gov.cn.nqyfm.cn http://www.morning.qztsq.cn.gov.cn.qztsq.cn http://www.morning.cbtn.cn.gov.cn.cbtn.cn http://www.morning.jtfcd.cn.gov.cn.jtfcd.cn