作为开发过程中手册浏览。1.1. 线程代码示例#include <thread> #include <iostream> // Linux -lpthread void ThreadMain() { std::cout << "begin sub thread main " << std::this_thread::get_id() << std::endl; for (int i = 0; i < 10; i++) { std::cout << "in thread " << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); // 1000ms } std::cout << "end sub thread main " << std::this_thread::get_id() << std::endl; } int main(int argc, char* argv[]) { std::cout << "main thread ID " << std::this_thread::get_id() << std::endl; // 线程创建启动 std::thread th(ThreadMain); std::cout << "begin wait sub thread " << std::endl; // 阻塞等待子线程退出 th.join(); std::cout << "end wait sub thread " << std::endl; return 0; } 1.2. 对象生命周期和线程等待和分离#include <thread> #include <iostream> // Linux -lpthread bool is_exit = false; void ThreadMain() { std::cout << "begin sub thread main " << std::this_thread::get_id() << std::endl; for (int i = 0; i < 10; i++) { if (!is_exit) break; std::cout << "in thread " << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); // 1000ms } std::cout << "end sub thread main " << std::this_thread::get_id() << std::endl; } int main(int argc, char *argv[]) { { std:: thread th(ThreadMain); //出错，thread对象被销毁 子线程还在运行 } { std::thread th(ThreadMain); th.detach(); // 子线程与主线程分离 守护线程 // 坑 ：主线程退出后 子线程不一定退出 // 可能造成的问题是主线程推出(主程序退出)，程序报错(主线程退出，全部资源释放) // 一般解决方案：通过 is_exit 来控制，起到通知作用 // 如果不做 detach，则必须维护 th 对象 } { std::thread th(ThreadMain); std::this_thread::sleep_for(std::chrono::seconds(1)); // 1000ms is_exit = true; // 通知子线程退出 std::cout << "主线程阻塞，等待子线程退出" << std::endl; th.join(); // 主线程阻塞，等待子线程退出 std::cout << "子线程已经退出！" << std::endl; } std::cin.get(); return 0; }课程中用的vs2019，可以看到thread类的源码，可以看到构造、析构的写法(若没有join则删除对象，释放空间)。如果调用detach，则分离，主线程中不用再关注该线程若join方式来精确控制子线程，则一般需要维系该线程对象，一般将th对象放在成员函数中当线程的执行结果不需要返回给主线程，或者可以通过其他方式通知主线程时，可以使用detach()。例如，一个后台日志记录线程，它只负责将日志信息写入文件，不需要和主线程交互，就可以使用detach()。当线程的执行时间不确定，或者可能比主线程更长时，可以使用detach()。例如，一个网络请求线程，它可能会因为网络延迟或者服务器故障而阻塞很久，如果使用join()，就会导致主线程也无法继续运行，影响用户体验。如果使用detach()，就可以让主线程继续响应用户操作，而网络请求线程在后台等待结果。当线程的执行逻辑和主线程无关，或者可以独立完成时，可以使用detach()。例如，一个音乐播放器程序，它可以创建一个音乐播放线程来控制音乐的播放、暂停、切换等操作，而主线程只负责显示界面和接收用户输入。这样的话，音乐播放线程就可以使用detach()，让它和主线程分离。detach()也有一些潜在的风险和问题，例如：detach()后的线程可能会访问已经被销毁的对象或变量，导致程序崩溃或者数据不一致。例如，如果一个线程要访问主线中的对象以及指针问题。detach()后的线程可能会在主线程结束后还没有执行完毕，导致程序异常退出或者资源泄露。例如，如果一个线程要写入文件或者数据库等外部资源。detach()后的线程可能会难以控制和监控，导致程序逻辑混乱或者性能下降。例如，如果一个程序创建了过多的detach()的线程。因此，在使用detach()时，需要注意以下几点：尽量避免在detach()的线程中访问主线中的对象或变量，或者使用同步机制来保护共享资源。尽量确保detach()的线程在主线结束前能够正常退出，或者提供一种机制来通知detach()的线程退出。尽量控制detach()的线程的数量和质量，避免过度消耗系统资源和影响程序稳定性。1.3. 全局函数作为线程入口分析参数传递内存操作#include <thread> #include <iostream> #include <string> //Linux -lpthread using namespace std; class Para { public: Para() { cout << "Create Para" << endl; } Para(const Para &p) { cout << "Copy Para" << endl; } ~Para() { cout << "Drop Para" << endl; } string name; }; void ThreadMain(int p1, float p2, string str, Para p4) { this_thread::sleep_for(100ms); cout << "ThreadMain " << p1 << " " << p2 << " " << str << " " << p4.name << endl; } int main(int argc, char *argv[]) { thread th; { float f1 = 12.1f; Para p; p.name = "test Para class"; //所有的参数做复制 th = thread(ThreadMain, 101, f1, "test string para", p);// 在这里会产生复制，thread对象在建立时候，会将参数复制 } th.join(); return 0; }如何避免复制，1.4. 线程函数传递指针和引用 中解释。1.4. 线程函数传递指针和引用#include <thread> #include <iostream> #include <string> //Linux -lpthread class Para { public: Para() { std::cout << "Create Para" << std::endl; } Para(const Para &p) { std::cout << "Copy Para" << std::endl; } ~Para() { std::cout << "Drop Para" << std::endl; } std::string name; }; void ThreadMain(int p1, float p2, std::string str, Para p4) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "ThreadMain " << p1 << " " << p2 << " " << str << " " << p4.name << std::endl; } void ThreadMainPtr(Para *p) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "ThreadMainPtr name = " << p->name << std::endl; } void ThreadMainRef(Para &p) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "ThreadMainPtr name = " << p.name << std::endl; } int main(int argc, char *argv[]) { { //传递引用 Para p; p.name = "test ref"; std::thread th(ThreadMainRef, std::ref(p));// ref 是为了标识为引用 th.join(); } std::cin.get(); { //传递线程指针 Para p; p.name = "test ThreadMainPtr name"; std::thread th(ThreadMainPtr, &p); //错误 ，线程访问的p空间会提前释放 // 解决方案是 1. 将 p 放在类成员当中 2. p 传入到堆 3. 静态 p // 所以 p 这个空间，一定要保证在整个线程运行期间，在他的生命周期内 th.detach(); } // Para 释放 std::cin.get(); { //传递线程指针 Para p; p.name = "test ThreadMainPtr name"; std::thread th(ThreadMainPtr, &p); th.join(); std::cin.get(); } std::thread th; { float f1 = 12.1f; Para p; p.name = "test Para class"; //所有的参数做复制 th = std::thread(ThreadMain, 101, f1, "test std::string para", p); } th.join(); return 0; }1.5. 使用成员函数作为线程入口并封装线程基类接口#include <thread> #include <iostream> #include <string> //Linux -lpthread class MyThread { public: //入口线程函数 void Main() { std::cout << "MyThread Main " << name << ":" << age; } std::string name; int age = 100; }; class XThread { public: virtual void Start() { is_exit_ = false; th_ = std::thread(&XThread::Main, this); } virtual void Stop() { is_exit_ = true; Wait(); } virtual void Wait() { if (th_.joinable()) th_.join(); } bool is_exit() { return is_exit_; } private: virtual void Main() = 0; std::thread th_; bool is_exit_ = false; }; class TestXThread : public XThread { public: void Main() override { std::cout << "TestXThread Main begin" << std::endl; while (!is_exit()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "." << std::flush;// flush保证及时刷新，cout的内容不会延迟 } std::cout << "TestXThread Main end" << std::endl; } std::string name; }; int main(int argc, char *argv[]) { TestXThread testth;// 进入派生类重载 testth.name = "TestXThread name "; testth.Start(); std::this_thread::sleep_for(std::chrono::seconds(3)); testth.Stop(); testth.Wait(); std::cin.get(); // 成员函数作为参数 MyThread myth; myth.name = "Test name 001"; myth.age = 20; std::thread th(&MyThread::Main, &myth); // &MyThread::Main 成员函数指针 // &myth 指当前对象地址(*this) th.join(); return 0; }1.6. lambda临时函数作为线程入口#include <thread> #include <iostream> #include <string> // test lambda thread class TestLambda { public: void Start() { std::thread th([this]() { std::cout << "name = " << name << std::endl; }); th.join(); } std::string name = "test lambda"; }; int main(int argc, char *argv[]) { std::thread th( [](int i) { std::cout << "test lmbda " << i << std::endl; }, 123 ); th.join(); TestLambda test; test.Start(); return 0; }1.7. 多线程的状态及其切换流程分析线程状态说明：初始化：线程正在创建初始化内存空间等，代码干预不多，从初始化到就绪有一定的消耗，所以才会引入线程池，为了减少类似消耗就绪：线程在就绪列表中，等待CPU调度运行：该线程正在运行阻塞：线程被阻塞挂起。Blocked状态包括：pend(锁、事件、信号量等阻塞)、suspend(主动pend)、delay(延时阻塞)、pendtime(因为锁、事件、信号量事件等超时等待)退出：线程运行结束，等待父线程回收其控制块资源(栈等，不会释放线程运行中创建的堆资源)1.8. 竞争状态和临界区介绍_互斥锁mutex代码演示临界区：读写共享数据的代码片段#include <thread> #include <iostream> #include <string> #include <mutex> // Linux -lpthread using namespace std; static mutex mux; // 尽晚申请，尽早释放 void TestThread() { for (;;) { // 获取锁资源，如果没有则阻塞等待 // mux.lock(); // 操作系统层面的锁 if (!mux.try_lock()) { // try_lock 可以帮助看到多线程竞争锁的情况 cout << "." << flush; this_thread::sleep_for(100ms); // 要进行停顿，否则会不停循环，将资源耗尽 // try_lock可以控制多线程的同时带来了性能开销 continue; } cout << "==============================" << endl; cout << "test 001" << endl; cout << "test 002" << endl; cout << "test 003" << endl; cout << "==============================" << endl; mux.unlock(); // 尽晚申请，尽早释放 this_thread::sleep_for(1000ms); } } int main(int argc, char* argv[]) { for (int i = 0; i < 10; i++) { thread th(TestThread); th.detach(); } std::cin.get(); return 0; } 1.9. 互斥锁的坑_线程抢占不到资源原因和解决方法#include <thread> #include <iostream> #include <string> #include <mutex> static std::mutex mux; void ThreadMainMux(int i) { for (;;) { mux.lock(); std::cout << i << "[in]" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(100)); mux.unlock(); std::this_thread::sleep_for( std::chrono::milliseconds(1)); // 这里，交给操作系统释放资源 // 总之就是不能在unlock之后，立刻lock。一个小坑，只看代码很可能难以发现 } } int main(int argc, char* argv[]) { for (int i = 0; i < 3; i++) { std::thread th(ThreadMainMux, i + 1); th.detach(); } std::cin.get(); return 0; } 1.10. 超时锁timed_mutex和可重入锁recursive#include <thread> #include <iostream> #include <string> #include <mutex> static std::mutex mux; void TestThread() { for (;;) { // 获取锁资源，如果没有则阻塞等待 // mux.lock(); // if (!mux.try_lock()) { std::cout << "." << std::flush; std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } std::cout << "==============================" << std::endl; std::cout << "test 001" << std::endl; std::cout << "test 002" << std::endl; std::cout << "test 003" << std::endl; std::cout << "==============================" << std::endl; mux.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } void ThreadMainMux(int i) { for (;;) { mux.lock(); std::cout << i << "[in]" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); mux.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } std::timed_mutex tmux; void ThreadMainTime(int i) { for (;;) { if (!tmux.try_lock_for(std::chrono::milliseconds(500))) { std::cout << i << "[try_lock_for timeout]" << std::endl; // 尝试获取一个名为tmux的定时互斥锁。如果锁已经被其他线程占用，并且在500毫秒内没有释放， // 则当前线程会返回false，否则会返回true并获取锁。 continue; } std::cout << i << "[in]" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(2000)); tmux.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } // try_lock_for : // 尝试锁定关联互斥。阻塞直至经过指定的 timeout_duration // 或获得锁，之先到来者。成功获得锁时返回 true ，否则返回 false 等效地调用 // mutex()->try_lock_for(timeout_duration) } std::recursive_mutex rmux; void Task1() { rmux.lock(); std::cout << "task1 [in]" << std::endl; rmux.unlock(); } void Task2() { rmux.lock(); std::cout << "task2 [in]" << std::endl; rmux.unlock(); } void ThreadMainRec(int i) { for (;;) { rmux.lock(); Task1(); std::cout << i << "[in]" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(2000)); Task2(); rmux.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } int main(int argc, char* argv[]) { for (int i = 0; i < 3; i++) { std::thread th(ThreadMainRec, i + 1); th.detach(); } std::cin.get(); for (int i = 0; i < 3; i++) { std::thread th(ThreadMainTime, i + 1); th.detach(); } std::cin.get(); for (int i = 0; i < 3; i++) { std::thread th(ThreadMainMux, i + 1); th.detach(); } std::cin.get(); for (int i = 0; i < 10; i++) { std::thread th(TestThread); th.detach(); } std::cin.get(); return 0; } 还是推荐一下cppreference.com的示例代码：timed_mutex 类是能用于保护数据免受多个线程同时访问的同步原语。以类似 mutex 的行为， timed_mutex 提供排他性非递归所有权语义。另外， timed_mutex 提供通过 try_lock_for() 和 try_lock_until() 方法试图带时限地要求 timed_mutex 所有权的能力。timed_mutex 类满足定时互斥与标准布局类型的所有要求。recursive_mutex 的使用场景之一是保护类中的共享状态，而类的成员函数可能相互调用:#include <iostream> #include <thread> #include <mutex> class X { std::recursive_mutex m; std::string shared; public: void fun1() { std::lock_guard<std::recursive_mutex> lk(m); shared = "fun1"; std::cout << "in fun1, shared variable is now " << shared << '\n'; } void fun2() { std::lock_guard<std::recursive_mutex> lk(m); shared = "fun2"; std::cout << "in fun2, shared variable is now " << shared << '\n'; fun1(); // 递归锁在此处变得有用 std::cout << "back in fun2, shared variable is " << shared << '\n'; }; }; int main() { X x; std::thread t1(&X::fun1, &x); std::thread t2(&X::fun2, &x); t1.join(); t2.join(); }1.11. 共享锁shared_mutex解决读写问题#include <thread> #include <iostream> #include <string> #include <mutex> #include <shared_mutex> // c++17 共享锁 // shared_mutex smux; // c++14 std::shared_timed_mutex stmux; void ThreadRead(int i) { for (;;) { stmux.lock_shared(); std::cout << i << " Read" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(500)); stmux.unlock_shared(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } void ThreadWrite(int i) { for (;;) { stmux.lock_shared(); // 读取数据 stmux.unlock_shared(); stmux.lock(); // 互斥锁 写入 std::cout << i << " Write" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(300)); stmux.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } int main(int argc, char* argv[]) { for (int i = 0; i < 3; i++) { std::thread th(ThreadWrite, i + 1); th.detach(); } for (int i = 0; i < 3; i++) { std::thread th(ThreadRead, i + 1); th.detach(); } std::cin.get(); return 0; } 共享锁中主要包含两个锁，一个是共享锁，一个是互斥锁。cppreference.com示例代码：其实这个示例更多的是学习两个锁的初始化方式..#include <iostream> #include <mutex> // 对于 std::unique_lock #include <shared_mutex> #include <thread> class ThreadSafeCounter { public: ThreadSafeCounter() = default; // 多个线程/读者能同时读计数器的值。 unsigned int get() const { std::shared_lock<std::shared_mutex> lock(mutex_); // 互斥元保护value_的读写，防止多个线程同时写value_,允许多个线程同时读value_ return value_; } // 只有一个线程/写者能增加/写线程的值。 void increment() { std::unique_lock<std::shared_mutex> lock(mutex_); // unique_lock和shared_lock都是互斥元，但是unique_lock可以在构造时指定锁的类型，而shared_lock只能是共享锁 value_++; } // 只有一个线程/写者能重置/写线程的值。 void reset() { std::unique_lock<std::shared_mutex> lock(mutex_); value_ = 0; } private: mutable std::shared_mutex mutex_; unsigned int value_ = 0; }; int main() { ThreadSafeCounter counter; auto increment_and_print = [&counter]() { for (int i = 0; i < 3; i++) { counter.increment(); std::cout << std::this_thread::get_id() << ' ' << counter.get() << '\n'; // 注意：写入 std::cout 实际上也要由另一互斥同步。省略它以保持示例简洁。 } }; std::thread thread1(increment_and_print); std::thread thread2(increment_and_print); thread1.join(); thread2.join(); } // 解释：下列输出在单核机器上生成。 thread1 开始时，它首次进入循环并调用 // increment() ， 随后调用 get() 。然而，在它能打印返回值到 std::cout // 前，调度器将 thread1 置于休眠 并唤醒 thread2 // ，它显然有足够时间一次运行全部三个循环迭代。再回到 thread1 ，它仍在首个 // 循环迭代中，它最终打印其局部的计数器副本的值，即 1 到 std::cout // ，再运行剩下二个循环。 // 多核机器上，没有线程被置于休眠，且输出更可能为递增顺序。 /* 可能的输出： 123084176803584 2 123084176803584 3 123084176803584 4 123084185655040 1 123084185655040 5 123084185655040 6 */ 其实上述代码很巧妙，也很可能是开发过程中难以调试的bug。1.12. 手动实现RAII管理mutex资源_锁自动释放这种方式是手动方式，c++11提供了RAII控制锁lock_guard，见：c++11RAII控制锁lock\_guard#include <thread> #include <iostream> #include <string> #include <mutex> #include <shared_mutex> // RAII class XMutex { // 这种简化的类的构造方式值得借鉴 哈 public: XMutex(mutex& mux) : mux_(mux) { std::cout << "Lock" << std::std::endl; mux.lock(); } ~XMutex() { std::cout << "Unlock" << std::std::endl; mux_.unlock(); // 借助析构函数释放资源，RAII主要就是这个思想 } private: mutex& mux_; }; static mutex mux; void TestMutex(int status) { XMutex lock(mux); if (status == 1) { std::cout << "=1" << std::std::endl; return; } else { std::cout << "!=1" << std::std::endl; return; } } int main(int argc, char* argv[]) { TestMutex(1); TestMutex(2); std::cin.get(); return 0; }1.13. c++11RAII控制锁lock_guardcppreference.com： 资源获取即初始化（Resource Acquisition Is Initialization），或称 RAII，是一种 C++ 编程技术，它将必须在使用前请求的资源（分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事物）的生命周期与一个对象的生存期相绑定。RAII 保证资源能够用于任何会访问该对象的函数（资源可用性是一种类不变式，这会消除冗余的运行时测试）。它也保证对象在自己生存期结束时会以获取顺序的逆序释放它控制的所有资源。类似地，如果资源获取失败（构造函数以异常退出），那么已经构造完成的对象和基类子对象所获取的所有资源就会以初始化顺序的逆序释放。这有效地利用了语言特性（对象生存期、退出作用域、初始化顺序以及栈回溯）以消除内存泄漏并保证异常安全。根据 RAII 对象的生存期在退出作用域时结束这一基本状况，此技术也被称为作用域界定的资源管理（Scope-Bound Resource Management，SBRM）。RAII 可以总结如下:将每个资源封装入一个类，其中：构造函数请求资源，并建立所有类不变式，或在它无法完成时抛出异常，析构函数释放资源并且决不会抛出异常；在使用资源时始终通过 RAII 类的满足以下要求的实例：自身拥有自动存储期或临时生存期，或具有与自动或临时对象的生存期绑定的生存期移动语义使得在对象间，跨作用域，以及在线程内外安全地移动所有权，而同时维护资源安全成为可能。(C++11 起)拥有 open()/close()、lock()/unlock()，或 init()/copyFrom()/destroy() 成员函数的类是典型的非 RAII 类的例子：std::mutex m; void bad() { m.lock(); // 请求互斥体 f(); // 如果 f() 抛出异常，那么互斥体永远不会被释放 if(!everything_ok()) return; // 提早返回，互斥体永远不会被释放 m.unlock(); // 只有 bad() 抵达此语句，互斥体才会被释放 } void good() { std::lock_guard<std::mutex> lk(m); // RAII类：互斥体的请求即是初始化 f(); // 如果 f() 抛出异常，那么就会释放互斥体 if(!everything_ok()) return; // 提早返回也会释放互斥体 } // 如果 good() 正常返回，那么就会释放互斥体std::lock_guard示例代码：#include <thread> #include <mutex> #include <iostream> int g_i = 0; std::mutex g_i_mutex; // 保护 g_i void safe_increment() { std::lock_guard<std::mutex> lock(g_i_mutex); ++g_i; std::cout << std::this_thread::get_id() << ": " << g_i << '\n'; // g_i_mutex 在锁离开作用域时自动释放 } int main() { std::cout << "main: " << g_i << '\n'; std::thread t1(safe_increment); std::thread t2(safe_increment); t1.join(); t2.join(); std::cout << "main: " << g_i << '\n'; }lock_guard建立在栈上，离开大括号后自动释放锁，查看lock_guard源码，对应析构函数可以看到。~lock_guard() _LIBCPP_THREAD_SAFETY_ANNOTATION(release_capability()) {__m_.unlock();}另一例：#include <thread> #include <iostream> #include <string> #include <mutex> #include <shared_mutex> // RAII class XMutex { public: XMutex(std::mutex& mux) : mux_(mux) { std::cout << "Lock" << std::endl; mux.lock(); } ~XMutex() { std::cout << "Unlock" << std::endl; mux_.unlock(); } private: std::mutex& mux_; }; static std::mutex mux; void TestMutex(int status) { XMutex lock(mux); if (status == 1) { std::cout << "=1" << std::endl; return; } else { std::cout << "!=1" << std::endl; return; } } static std::mutex gmutex; void TestLockGuard(int i) { gmutex.lock(); { // 已经拥有锁，不lock std::lock_guard<std::mutex> lock(gmutex, std::adopt_lock); // 结束释放锁 } { std::lock_guard<std::mutex> lock(gmutex); std::cout << "begin thread " << i << std::endl; } for (;;) { { std::lock_guard<std::mutex> lock(gmutex); std::cout << "In " << i << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } int main(int argc, char* argv[]) { for (int i = 0; i < 3; i++) { std::thread th(TestLockGuard, i + 1); th.detach(); } TestMutex(1); TestMutex(2); std::cin.get(); return 0; }lock_guard什么时候释解锁，如果将其放在大括号内，则互斥量充满整个大括号，那么着是不是我们理想的效果，需要注意。另外，临界区内不应该包含sleep()，sleep()的时候，锁还在占用，资源还在占用状态，这样会存在巨大风险。lock_guard构造函数支持两种： explicit lock_guard(mutex_type& __m) _LIBCPP_THREAD_SAFETY_ANNOTATION(acquire_capability(__m)) : __m_(__m) {__m_.lock();} lock_guard(mutex_type& __m, adopt_lock_t) _LIBCPP_THREAD_SAFETY_ANNOTATION(requires_capability(__m)) : __m_(__m) {}第一种直接mutex类型即可完成构造，也就是有lock()和unlock()就可以。第二种知识将锁存下来，但是并未进行锁操作，相同的，析构函数都会unlock()private: lock_guard(lock_guard const&) = delete; lock_guard& operator=(lock_guard const&) = delete;lock_guard无法转移，不能由一个lock_guard到另一个lock_guard。需要注意的是，lock_guard默认互斥资源没有被锁。static std::mutex gmutex; void TestLockGuard(int i) { gmutex.lock(); { std::lock_guard<std::mutex> lock(gmutex); } }gmutex已经在lock_guard之外锁住了资源，上述代码会抛出异常，如果不想锁相关资源，那么利用重载，调用不同构造，传入一个常量参数即可。1.14. unique_lock可临时解锁控制超时的互斥体包装器unique_lock C++11 实现可移动的互斥体所有权包装器支持临时释放锁 unlock支持 adopt_lock（已经拥有锁，不加锁，出栈区会释放）支持 defer_lock （延后拥有，不加锁，出栈区不释放）支持 try_to_lock 尝试获得互斥的所有权而不阻塞 ，获取失败退出栈区不会释放，通过 owns_lock()函数判断支持超时参数，超时不拥有锁#include <thread> #include <iostream> #include <string> #include <mutex> #include <shared_mutex> // RAII class XMutex { public: XMutex(std::mutex& mux) : mux_(mux) { std::cout << "Lock" << std::endl; mux.lock(); } ~XMutex() { std::cout << "Unlock" << std::endl; mux_.unlock(); } private: std::mutex& mux_; }; static std::mutex mux; void TestMutex(int status) { XMutex lock(mux); if (status == 1) { std::cout << "=1" << std::endl; return; } else { std::cout << "!=1" << std::endl; return; } } static std::mutex gmutex; void TestLockGuard(int i) { gmutex.lock(); { // 已经拥有锁，不lock std::lock_guard<std::mutex> lock(gmutex, std::adopt_lock); // 结束释放锁 } { std::lock_guard<std::mutex> lock(gmutex); std::cout << "begin thread " << i << std::endl; } for (;;) { { std::lock_guard<std::mutex> lock(gmutex); std::cout << "In " << i << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } int main(int argc, char* argv[]) { { static std::mutex mux; { // 基础用法示例，可以先unlock()，然后lock() std::unique_lock<std::mutex> lock(mux); lock.unlock(); // 临时释放锁 lock.lock(); // 退出大括号后依旧会释放锁，和 unique_lock 一样 } { // 已经拥有锁 不锁定，退出栈区解锁 mux.lock(); std::unique_lock<std::mutex> lock(mux, std::adopt_lock); } { // 延后加锁 不拥有 退出栈区不解锁 std::unique_lock<std::mutex> lock(mux, std::defer_lock); // 加锁 退出栈区解锁 lock.lock(); } { // mux.lock(); // 尝试加锁 不阻塞 失败不拥有锁 std::unique_lock<std::mutex> lock(mux, std::try_to_lock); if (lock.owns_lock()) { std::cout << "owns_lock" << std::endl; } else { std::cout << "not owns_lock" << std::endl; } } } for (int i = 0; i < 3; i++) { std::thread th(TestLockGuard, i + 1); th.detach(); } TestMutex(1); TestMutex(2); std::cin.get(); return 0; }注意： defer_lock功能时，__owns_(false)，同步析构时不会解锁unique_lock(mutex_type& __m, defer_lock_t) _NOEXCEPT : __m_(_VSTD::addressof(__m)), __owns_(false) {} ... ~unique_lock() { if (__owns_) __m_->unlock(); }只能手动去lock或unlock1.15. C++14shared_lock共享锁包装器以下这段代码值得商榷，主要看main函数中前两个大括号内的使用方法。#include <thread> #include <iostream> #include <string> #include <mutex> #include <shared_mutex> // Linux -lpthread // RAII class XMutex { public: XMutex(std::mutex& mux) : mux_(mux) { std::cout << "Lock" << std::endl; mux.lock(); } ~XMutex() { std::cout << "Unlock" << std::endl; mux_.unlock(); } private: std::mutex& mux_; }; static std::mutex mux; void TestMutex(int status) { XMutex lock(mux); if (status == 1) { std::cout << "=1" << std::endl; return; } else { std::cout << "!=1" << std::endl; return; } } static std::mutex gmutex; void TestLockGuard(int i) { gmutex.lock(); { // 已经拥有锁，不lock std::lock_guard<std::mutex> lock(gmutex, std::adopt_lock); // 结束释放锁 } { std::lock_guard<std::mutex> lock(gmutex); std::cout << "begin thread " << i << std::endl; } for (;;) { { std::lock_guard<std::mutex> lock(gmutex); std::cout << "In " << i << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } int main(int argc, char* argv[]) { { // 共享锁 static std::shared_timed_mutex tmux; // 读取锁 共享锁 { // 调用共享锁 std::shared_lock<std::shared_timed_mutex> lock(tmux); std::cout << "read data" << std::endl; // 退出栈区 释放共享锁 } // 写入锁 互斥锁 { // 这里不能先锁共享锁，然后锁互斥锁，死锁 std::unique_lock<std::shared_timed_mutex> lock(tmux); std::cout << "write data" << std::endl; } } { static std::mutex mux; { std::unique_lock<std::mutex> lock(mux); lock.unlock(); // 临时释放锁 lock.lock(); } { // 已经拥有锁 不锁定，退出栈区解锁 mux.lock(); std::unique_lock<std::mutex> lock(mux, std::adopt_lock); } { // 延后加锁 不拥有 退出栈区不解锁 std::unique_lock<std::mutex> lock(mux, std::defer_lock); // 加锁 退出栈区解锁 lock.lock(); } { // mux.lock(); // 尝试加锁 不阻塞 失败不拥有锁 // unique_lock<mutex> lock(mux, try_to_lock); // 锁超时 阻塞指定时间 失败不拥有锁 std::unique_lock<std::mutex> lock(mux, std::chrono::milliseconds(500)); if (lock.owns_lock()) { std::cout << "owns_lock" << std::endl; } else { std::cout << "not owns_lock" << std::endl; } } } for (int i = 0; i < 3; i++) { std::thread th(TestLockGuard, i + 1); th.detach(); } TestMutex(1); TestMutex(2); std::cin.get(); return 0; }1.16. c++17scoped_lock解决互锁造成的死锁问题使用场景：在一个临界区代码中需要使用到两个锁，先用第一个锁lock，再用第二个锁lock。 会出现问题：第一个线程把第一个锁锁住，另外一个线程把第二个锁锁住。 紧接着要访问第二个锁，会造成阻塞。 因为业务代码的问题，很难将锁的顺序定死，在这里用到scoped_lock 测试很难复线，很难测示例代码：#include <thread> #include <iostream> #include <string> #include <mutex> #include <shared_mutex> // Linux -lpthread // RAII class XMutex { public: XMutex(std::mutex& mux) : mux_(mux) { std::cout << "Lock" << std::endl; mux.lock(); } ~XMutex() { std::cout << "Unlock" << std::endl; mux_.unlock(); } private: std::mutex& mux_; }; static std::mutex mux; void TestMutex(int status) { XMutex lock(mux); if (status == 1) { std::cout << "=1" << std::endl; return; } else { std::cout << "!=1" << std::endl; return; } } static std::mutex gmutex; void TestLockGuard(int i) { gmutex.lock(); { // 已经拥有锁，不lock std::lock_guard<std::mutex> lock(gmutex, std::adopt_lock); // 结束释放锁 } { std::lock_guard<std::mutex> lock(gmutex); std::cout << "begin thread " << i << std::endl; } for (;;) { { std::lock_guard<std::mutex> lock(gmutex); std::cout << "In " << i << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } static std::mutex mux1; static std::mutex mux2; void TestScope1() { // 模拟死锁 停100ms等另一个线程锁mux2 std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << std::this_thread::get_id() << " begin mux1 lock" << std::endl; // mux1.lock(); std::cout << std::this_thread::get_id() << " begin mux2 lock" << std::endl; // mux2.lock(); //死锁 // c++11 // lock(mux1, mux2); // c++17 std::scoped_lock lock(mux1, mux2); // 解决死锁 std::cout << "TestScope1" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 实际中临界资源中不要sleep // mux1.unlock(); // mux2.unlock(); } void TestScope2() { std::cout << std::this_thread::get_id() << " begin mux2 lock" << std::endl; mux2.lock(); std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << std::this_thread::get_id() << " begin mux1 lock" << std::endl; mux1.lock(); // 死锁 std::cout << "TestScope2" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1500)); mux1.unlock(); mux2.unlock(); } int main(int argc, char* argv[]) { { // 演示死锁情况 { std::thread th(TestScope1); th.detach(); } { std::thread th(TestScope2); th.detach(); } } std::cin.get(); { // 共享锁 static std::shared_timed_mutex tmux; // 读取锁 共享锁 { // 调用共享锁 std::shared_lock<std::shared_timed_mutex> lock(tmux); std::cout << "read data" << std::endl; // 退出栈区 释放共享锁 } // 写入锁 互斥锁 { std::unique_lock<std::shared_timed_mutex> lock(tmux); std::cout << "write data" << std::endl; } } { static std::mutex mux; { std::unique_lock<std::mutex> lock(mux); lock.unlock(); // 临时释放锁 lock.lock(); } { // 已经拥有锁 不锁定，退出栈区解锁 mux.lock(); std::unique_lock<std::mutex> lock(mux, std::adopt_lock); } { // 延后加锁 不拥有 退出栈区不解锁 std::unique_lock<std::mutex> lock(mux, std::defer_lock); // 加锁 退出栈区解锁 lock.lock(); } { // mux.lock(); // 尝试加锁 不阻塞 失败不拥有锁 std::unique_lock<std::mutex> lock(mux, std::try_to_lock); if (lock.owns_lock()) { std::cout << "owns_lock" << std::endl; } else { std::cout << "not owns_lock" << std::endl; } } } for (int i = 0; i < 3; i++) { std::thread th(TestLockGuard, i + 1); th.detach(); } TestMutex(1); TestMutex(2); std::cin.get(); return 0; } 析构函数(__unlock_unpack)： ~scoped_lock() { typedef typename __make_tuple_indices<sizeof...(_MArgs)>::type _Indices; __unlock_unpack(_Indices{}, __t_); }1.17. 项目案例线程通信使用互斥锁和list实现线程通信thread_msg_server.md xmsg_server.md xthread.md xmsg_server.md xthread.md1.18. 条件变量应用场景_生产者消费者信号处理步骤（一）改变共享变量的线程步骤准备好信号量std::condition_variable cv;获得 std::mutex （常通过 std::unique_lock ）unique_lock lock(mux);在获取锁时进行修改msgs_.push_back(data);释放锁并通知读取线程lock.unlock(); cv.notify_one(); // 通知一个等待信号线程 cv.notify_all(); // 通知所有等待信号线程 一般是程序或者业务结束时候通知所有（二）等待信号读取共享变量的线程步骤获得与改变共享变量线程共同的mutexunique_lock lock(mux);wait() 等待信号通知 2.1 无lambada 表达式//解锁lock,并阻塞等待 notify_one notify_all 通知 cv.wait(lock); //接收到通知会再次获取锁标注，也就是说如果此时mux资源被占用，wait函数会阻塞 msgs_.front(); //处理数据 msgs_.pop_front();2.2 lambada 表 达 式 cv.wait(lock, [] {return !msgs_.empty();});只在 std::unique_lock<std::mutex> 上工作的 std::condition_variablevoid wait(unique_lock<mutex>& _Lck) { // wait for signal // Nothing to do to comply with // LWG‐2135 because std::mutex lock/unlock are nothrow _Check_C_return(_Cnd_wait(_Mycnd(), _Lck.mutex()‐> _Mymtx())); } template <class _Predicate> void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) { // wait for signal and test predicate while (!_Pred()) { wait(_Lck); } }1.19. condition_variable代码示例读写线程同步一个生产者线程，写入，然后通知多个线程处理。重点是看wait函数，是否锁定的条件。#include <thread> #include <iostream> #include <mutex> #include #include <string> #include <sstream> std::list<std::string> msgs_; std::mutex mux; std::condition_variable cv; void ThreadWrite() { for (int i = 0;; i++) { std::stringstream ss; ss << "Write msg " << i; std::unique_lock<std::mutex> lock(mux); msgs_.push_back(ss.str()); lock.unlock(); // 要先解锁，再发送信号，否则会造成死锁(在cv.wait时会对互斥量上锁) cv.notify_one(); // 发送信号 // cv.notify_all(); std::this_thread::sleep_for(std::chrono::seconds(3)); } } void ThreadRead(int i) { for (;;) { std::cout << "read msg" << std::endl; std::unique_lock<std::mutex> lock(mux); // cv.wait(lock);//解锁、阻塞等待信号 cv.wait(lock, [i] { std::cout << i << " wait" << std::endl; return !msgs_.empty(); }); // 获取信号后锁定 while (!msgs_.empty()) { std::cout << i << " read " << msgs_.front() << std::endl; msgs_.pop_front(); } } } int main(int argc, char* argv[]) { std::thread th(ThreadWrite); th.detach(); for (int i = 0; i < 3; i++) { std::thread th(ThreadRead, i + 1); th.detach(); } std::cin.get(); return 0; }1.20. 条件变量应用线程通信解决线程退出时的阻塞问题thread_msg_server_condition.cpp xmsg_server.cpp xthread.cpp xmsg_server.h xthread.h1.21. promise和future多线程异步传值何时获取线程结果是不确定的，也就是启动线程和获取结果是在两个接口当中。std::promise和std::future在C++中是用于同步两个或更多并发任务的机制。试想一下，你有两个线程，这两个线程需要共享数据。在这种情况下，你可能会使用std::promise和std::future来进行同步。让我们先来看一下每一个的基本定义：std::promise：在某个线程中储存一个值或异常，这个储存的值或异常可被另一个关联的std::future对象获取。std::future：在某个线程中获取一个值或异常，这个值或异常是由之前关联的std::promise对象设置的。下面是一些具体的使用场景：多线程数据交换：你可以在一个线程内设置promise，然后在另一个线程中通过与此promise对应的future来获取这个promise设定的结果。异步操作：当你要在另一个线程中异步执行一些操作并获取结果时，可以使用promise和future机制。你先创建一个promise，然后将这个promise的future交给异步执行的函数。这个异步函数在完成计算后会设置promise的值。之后，主线程可以通过检查future的状态，或者直接调用future的get函数来获取异步操作的结果。将任务分离为生产者和消费者：这是一种经典的并发设计模式，生产者生产数据并设置promise的值，消费者通过future获取这些数据进行处理。这样可以有效地分离两种类型的任务，提高程序的结构化和并发性能。线程间的同步：Future对象的get()会阻塞主线程，直到promised的值被设置，这样就能保证在继续执行程序前，必要的数据或计算结果已经准备好。一个demo： // #define _DEBUG #ifdef _DEBUG #include <iostream> #include <cstddef> #include <utility> #include <iostream> #include <future> #include <thread> int main() { // 来自 packaged_task 的 future std::packaged_task<int()> task([]() { return 7; }); // 包装函数 std::future<int> f1 = task.get_future(); // 获取 future std::thread(std::move(task)).detach(); // 在线程上运行 // 来自 async() 的 future std::future<int> f2 = std::async(std::launch::async, []() { return 8; }); // 来自 promise 的 future std::promise<int> p; std::future<int> f3 = p.get_future(); std::thread([&p] { p.set_value_at_thread_exit(9); }).detach(); std::cout << "Waiting..." << std::flush; f1.wait(); f2.wait(); f3.wait(); std::cout << "Done!\nResults are: " << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n'; } #endif #ifndef _DEBUG #include <thread> #include <iostream> #include <future> #include <string> void TestFuture(std::promise<std::string> p) { std::cout << "begin TestFuture" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(3)); std::cout << "begin set value" << std::endl; p.set_value("TestFuture value"); std::this_thread::sleep_for(std::chrono::seconds(3)); std::cout << "end TestFuture" << std::endl; } int main(int argc, char* argv[]) { // 异步传输变量存储 std::promise<std::string> p; // 用来获取线程异步值获取 auto future = p.get_future(); auto th = std::thread(TestFuture, std::move(p)); std::cout << "begin future.get()" << std::endl; std::cout << "future get() = " << future.get() << std::endl; std::cout << "end future.get()" << std::endl; th.join(); std::cin.get(); return 0; } #endifclang编译器：begin future.get() future get() = begin TestFuture begin set value TestFuture value end future.get() end TestFutureMSVC编译器：begin future.get() begin TestFuture begin set value future get() = TestFuture value end future.get() end TestFuture总之end future.get()和end TestFuture的顺序可以看出，get返回不受线程是否退出影响的，另外get是阻塞的，一旦set_value就会取消。1.22. packaged_task 异步调用函数打包#include <thread> #include <iostream> #include <future> #include <string> std::string TestPack(int index) { std::cout << "begin Test Pack " << index << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟阻塞 return "Test Pack return"; } int main(int argc, char *argv[]) { std::packaged_task<std::string(int)> task(TestPack); auto result = task.get_future(); std::thread th(std::move(task), 101); // std::move将task的所有权从当前线程移交给新创建的线程th，因为std::packaged_task对象不可以被复制，只能被移动 std::cout << "begin result get" << std::endl; // 测试是否超时 for (int i = 0; i < 30; i++) { if (result.wait_for(std::chrono::milliseconds(100)) != std::future_status::ready) { continue; } } if (result.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout) { std::cout << "wait result timeout" << std::endl; } else std::cout << "result get " << result.get() << std::endl; th.join(); std::cin.get(); return 0; }这个代码演示了使用线程（特别是使用std::packaged_task）的几个理由和其优点：并发执行任务：这段代码中的TestPack函数需要耗费2秒（由于std::this_thread::sleep_for(std::chrono::seconds(2))）。如果不使用线程，main()函数会被阻塞，直到TestPack函数执行完成。但是，通过在新线程中运行TestPack，main()函数可以同时执行其它任务。这样，你就可以充分利用多核CPU，并且在等待一个耗时操作（比如I/O操作）完成的同时，运行其他任务。获取异步任务的结果： std::packaged_task将任务的结果存储在std::future对象中。result.get()允许你在任务完成后获得其结果。这意味着你可以在任何你想获取结果的地方获取到它，而不管它是在哪个线程中被计算出来的。方便处理耗时操作：std::packaged_task和std::future在处理耗时操作时非常有用。例如，你可以通过检查std::future的状态来查看操作是否已完成，而不是不断轮询或者阻塞直到操作完成。在上述代码中，我们通过不断检查result的状态来查看TestPack函数是否已完成。总的来说，使用线程和std::packaged_task，可以让代码并发执行任务，不阻塞主线程，同时便于获取异步任务的结果，处理耗时操作。 cppreference类模板 std::packaged_task 包装任何可调用 (Callable) 目标（函数、 lambda 表达式、 bind 表达式或其他函数对象），使得能异步调用它。其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中。正如 std::function ， std::packaged_task 是多态、具分配器的容器：可在堆上或以提供的分配器分配存储的可调用对象。#include <iostream> #include <cmath> #include <thread> #include <future> #include <functional> // 避免对 std::pow 重载集消歧义的独有函数 int f(int x, int y) { return std::pow(x, y); } void task_lambda() { std::packaged_task<int(int, int)> task( [](int a, int b) { return std::pow(a, b); }); std::future<int> result = task.get_future(); task(2, 9); std::cout << "task_lambda:\t" << result.get() << '\n'; } void task_bind() { std::packaged_task<int()> task(std::bind(f, 2, 11)); std::future<int> result = task.get_future(); task(); std::cout << "task_bind:\t" << result.get() << '\n'; } void task_thread() { std::packaged_task<int(int, int)> task(f); std::future<int> result = task.get_future(); std::thread task_td(std::move(task), 2, 10); task_td.join(); std::cout << "task_thread:\t" << result.get() << '\n'; } int main() { task_lambda(); task_bind(); task_thread(); }1.23. async创建异步线程替代thread这段代码让我们来看看 C++ 中的异步编程。"async" 和 "future" 使用来创建和同步异步操作。这里的 "async" 函数有两个主要作用：创建一个异步任务。该任务将在单独的线程（可能是现有的线程）上执行。通过这种方式，代码可以同时在多个线程上运行，从而改善程序的性能。返回一个 "future" 对象。这个对象代表了异步任务的结果。当异步任务完成时，可以使用 get() 函数从 "future" 对象中获取结果。如果调用 get() 时任务仍然在运行，那么调用 get() 的线程将阻塞，直到任务完成为止。现在，让我们来具体看看这段代码中的异步操作。在这段代码中，我们创建了两个异步任务。我们使用了两种不同的方式来使用 std::async 函数：对于第一个异步任务，我们使用 async(std::launch::deferred, TestAsync, 100)。这里的 std::launch::deferred 参数意味着异步任务将会在 future.get() 被调用时才执行，而不是立即执行。这种方式下，并没有创建新的线程，而是复用了主线程。对于第二个异步任务，我们使用 async(TestAsync, 101)。在这个调用方式中， std::async 函数将立即创建一个新的线程（除非系统决定复用现有的线程）并开始执行 TestAsync。 TestAsync 任务的执行跟主线程是并行的，因此这是真正的异步调用。你可以通过 std::this_thread::get_id() 的不同输出来区分是否创建了新的线程。在不创建新线程的情况下，std::this_thread::get_id() 的输出将会和主线程的线程 ID 相同。若是创建了新线程，则线程 ID 将会不同。#include <thread> #include <iostream> #include <future> #include <string> std::string TestAsync(int index) { std::cout << index << " begin in TestAsync " << std::this_thread::get_id() << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); return "TestAsync string return"; } int main(int argc, char* argv[]) { std::cout << "main thread id " << std::this_thread::get_id() << std::endl; // 不创建线程启动异步任务 std::cout << "==不创建线程启动异步任务==" << std::endl; auto future = async(std::launch::deferred, TestAsync, 100); std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "begin future get " << std::endl; std::cout << "future.get() = " << future.get() << std::endl; std::cout << "end future get" << std::endl; // 创建异步线程 std::cout << "=====创建异步线程====" << std::endl; auto future2 = async(TestAsync, 101); std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "begin future2 get " << std::endl; std::cout << "future2.get() = " << future2.get() << std::endl; std::cout << "end future2 get" << std::endl; // 注意get只能调用一次，为了防止出错，用try catch包住 getchar(); return 0; }std::async 和 std::thread 都是 C++11 中用于创建并发任务的方式。不过它们的使用方式和关注点有些不同。std::thread: 它是比较底层的线程创建和操作方式。创建一个 std::thread 会产生一个新的操作系统线程和对应的栈空间，并立即开始并发执行。std::thread 提供了更多的控制，你可以细粒度地管理和调度线程，包括线程优先级、线程间同步、线程局部存储等。但是，使用 std::thread 需要负责处理所有线程同步的问题，比如数据竞争、死锁等，并且还需要手动管理线程的生命周期，比如合适的调用 join() 或 detach()。std::thread 的使用场景通常是在性能优先，且你愿意并熟悉如何处理多线程编程中各种复杂问题的时候。std::async和std::future：这是一种更高层次的并发抽象，主要关注的是任务并发，而不是线程并发。std::async 在创建任务的同时，返回一个std::future，代表异步任务的结果。std::future::get将阻塞等待任务完成并返回结果。任务在何时何地运行，是由实现决定的，可以是新建的线程，也可以是任务队列中的现有线程，甚至可以是调用 get() 的线程（当启动策略是 std::launch::deferred 时）。std::async 自动处理线程的生命周期管理，并为你处理部分线程同步问题。同时，由于使用 std::future 获取结果，也帮你处理了结果的返回和异常处理等问题。std::async 通常适用于需要并发并希望简化多线程编程的场景。所以，你要根据实际的需求和上下文来选择使用 std::async 还是 std::thread，如果你需要对线程有更细的控制，或者需要优化到OS级别的并发，那么 std::thread 可能是一个更好的选择。如果你更关注任务结果，并愿意将具体的运行方式交给系统来决定，希望能简化并发编程，确保异常安全，那么 std::async 可能是更合适的选择。1.23.1. std::launch::async和std::launch::deferred在C++中，std::launch是一个枚举类型，它与std::async函数一起使用，以控制异步操作的行为。这个函数主要用于异步的执行一个任务，返回一个std::future对象，你可以通过这个对象来获取任务的结果。std::launch有两个可能的值：std::launch::async和std::launch::deferred，这两个选项告诉std::async如何执行给定的任务。std::launch::async：当使用该选项时，std::async会尝试在另一个线程上立即启动所提供的任务。这意味着任务会并行执行，与主线程同时进行。当你需要任务并行执行，不阻塞当前线程时使用std::launch::async。std::future对象会与一个正在运行的异步任务相关联，你可以等待或获取这个任务的结果。std::launch::deferred：当使用该选项时，任务不会立即启动，而是延迟到对应的std::future对象的get()或wait()成员函数被调用时才执行。这意味着任务会在这些函数被调用时在调用者线程上执行，也就是说，它实际上变成了惰性求值。当你不需要立刻执行任务，或者希望在将来的某个点上，根据需要执行任务时使用std::launch::deferred。如果从未调用get()或wait()，则对应的任务可能永远不会执行。使用std::async时可以选择给这两个选项中的一个，或者不给，不给的话，实现可以自行选择是立即执行还是延迟执行。如果你想要确保异步任务的执行方式，你应该指定std::launch::async或std::launch::deferred。这是一个简单的例子，展示了如何使用std::async和std::launch::async启动一个异步任务：#include <iostream> #include <future> #include <thread> void task() { // 假设的耗时任务 std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout << "Task completed" << std::endl; } int main() { auto start = std::chrono::high_resolution_clock::now(); // 启动异步任务 std::future<void> result = std::async(std::launch::deferred, task); std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "Main thread continues" << std::endl; // 等待异步任务完成 result.wait(); auto end = std::chrono::high_resolution_clock::now(); // 计算时间差 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start) .count(); std::cout << "Function execution took " << duration << " microseconds." << std::endl; return 0; } 在这个例子中，任务会在一个新的线程中异步地启动。当main函数继续执行时，任务也在并行运行。一旦调用了result.wait()，主线程将会等待直到异步任务完成。如果改用std::launch::deferred，则任务会在调用result.wait()或result.get()时在主线程上同步执行。1.24. c++多核计算分析并实现base16编码一个简单实例#include <thread> #include <iostream> #include <string> #include <vector> #include <chrono> static const char base16[] = "0123456789abcdef"; void Base16Encode(const unsigned char* data, int size, unsigned char* out) { for (int i = 0; i < size; i++) { unsigned char d = data[i]; // 0000 0000 // 1234 5678 >>4 0000 1234 // 1234 5678 & 0000 1111 0000 5678 char a = base16[d >> 4]; char b = base16[d & 0x0F]; out[i * 2] = a; out[i * 2 + 1] = b; } } // C++11 多核base16编码 void Base16EncodeThread(const std::vector<unsigned char>& data, std::vector<unsigned char>& out) { int size = data.size(); int th_count = std::thread::hardware_concurrency(); // 系统支持的线程核心数 // 切片数据 int slice_count = size / th_count; // 余数丢弃 if (size < th_count) // 只切一片 { th_count = 1; slice_count = size; } // 准备好线程 std::vector<std::thread> ths; ths.resize(th_count); // 任务分配到各个线程 for (int i = 0; i < th_count; i++) { // 1234 5678 9abc defg hi int offset = i * slice_count; int count = slice_count; // 最后一个线程 if (th_count > 1 && i == th_count - 1) { count = slice_count + size % th_count; } // cout << offset << ":" << count << endl; ths[i] = std::thread(Base16Encode, data.data() + offset, count, out.data()); } // 等待所有线程处理结束 for (auto& th : ths) { th.join(); } } int main(int argc, char* argv[]) { std::string test_data = "测试base16编码"; unsigned char out[1024] = {0}; Base16Encode((unsigned char*)test_data.data(), test_data.size(), out); std::cout << "base16:" << out << std::endl; // 初始化测试数据 std::vector<unsigned char> in_data; in_data.resize(1024 * 1024 * 20); // 20M // in_data.data(); for (int i = 0; i < in_data.size(); i++) { in_data[i] = i % 256; } std::vector<unsigned char> out_data; out_data.resize(in_data.size() * 2); // 测试单线程base16编码效率 { std::cout << "单线程base16开始计算" << std::endl; auto start = std::chrono::system_clock::now(); Base16Encode(in_data.data(), in_data.size(), out_data.data()); auto end = std::chrono::system_clock::now(); auto duration = duration_cast<std::chrono::milliseconds>(end - start); std::cout << "编码：" << in_data.size() << "字节数据花费" << double(duration.count()) << "毫秒" << std::endl; // cout << out_data.data() << endl; } // 测试c++11 多线程Base16编码 { std::cout << "c++11 多线程Base16编码 开始计算" << std::endl; auto start = std::chrono::system_clock::now(); Base16EncodeThread(in_data, out_data); auto end = std::chrono::system_clock::now(); auto duration = duration_cast<std::chrono::milliseconds>(end - start); std::cout << "编码：" << in_data.size() << "字节数据花费" << double(duration.count()) << "毫秒" << std::endl; // cout << out_data.data() << endl; } std::cin.get(); return 0; }c++11实现多核base16编码并与单核性能测试对比请注意一点，hardware_concurrency，如果线程数多余系统硬件线程数，如果您在一个仅支持两个硬件线程的计算机上创建四个线程，这可能不会产生立即明显的负面影响，但以下是一些可能的影响和考虑因素：上下文切换： 当有多个线程可运行时，操作系统需要在它们之间进行上下文切换。每次切换都涉及保存当前线程的状态并加载下一个线程的状态，这些操作会产生开销。如果线程数量远多于处理器核心，这种上下文切换的成本可能会显著影响性能。资源竞争： 如果线程需要共享资源（如内存、I/O设备等），那么多个线程可能会导致竞争条件和锁争用。这些因素会降低并发性能。线程调度开销： 线程调度器需要跟踪更多的线程，分配CPU时间可能会变得更复杂，并导致额外的开销。应用逻辑复杂性： 编写多线程代码需要仔细的设计来避免死锁、竞态条件和其他多线程问题。线程越多，管理这些问题的难度就越大。然而，如果这些线程不会经常同时运行（即它们大部分时间都在等待外部事件或I/O操作完成），那么创建多于处理器核心数量的线程并不一定会对性能产生负面影响。实际上，这种情况下添加更多的线程可能会帮助提高CPU利用率，因为当其中的一些线程由于I/O操作而阻塞时，其他线程可以继续执行处理。对于I/O密集型应用（比如网络服务器或数据库应用），通常可以通过创建远多于CPU核心数的线程来提高并发性和吞吐量，因为这些线程大部分时间都在等待I/O操作的完成。总之，创建的线程数量是否会对应用程序产生负面影响取决于多种因素，包括应用程序的性能要求、线程的实际工作负载，以及系统的整体行为。通常，在设计多线程应用时，开发者需要仔细评估和测试以确保理想的性能。#include <thread> #include <iostream> #include <string> #include <vector> #include <chrono> #include <execution> using namespace std; using namespace chrono; static const char base16[] = "0123456789abcdef"; void Base16Encode(const unsigned char* data, int size, unsigned char* out) { for (int i = 0; i < size; i++) { unsigned char d = data[i]; // 0000 0000 // 1234 5678 >>4 0000 1234 // 1234 5678 & 0000 1111 0000 5678 char a = base16[d >> 4]; char b = base16[d & 0x0F]; out[i * 2] = a; out[i * 2 + 1] = b; } } // C++11 多核base16编码 void Base16EncodeThread(const vector<unsigned char>& data, vector<unsigned char>& out) { int size = data.size(); int th_count = thread::hardware_concurrency(); // 系统支持的线程核心数 // 切片数据 int slice_count = size / th_count; // 余数丢弃 if (size < th_count) // 只切一片 { th_count = 1; slice_count = size; } // 准备好线程 vector<thread> ths; ths.resize(th_count); // 任务分配到各个线程 for (int i = 0; i < th_count; i++) { // 1234 5678 9abc defg hi int offset = i * slice_count; int count = slice_count; // 最后一个线程 if (th_count > 1 && i == th_count - 1) { count = slice_count + size % th_count; } // cout << offset << ":" << count << endl; ths[i] = thread(Base16Encode, data.data() + offset, count, out.data()); } // 等待所有线程处理结束 for (auto& th : ths) { th.join(); } } int main(int argc, char* argv[]) { string test_data = "测试base16编码"; unsigned char out[1024] = {0}; Base16Encode((unsigned char*)test_data.data(), test_data.size(), out); cout << "base16:" << out << endl; // 初始化测试数据 vector<unsigned char> in_data; in_data.resize(1024 * 1024 * 200); // 20M // in_data.resize(32); //20M // in_data.data(); // for (int i = 0; i < in_data.size(); i++) //{ // in_data[i] = i % 256; // } vector<unsigned char> out_data; out_data.resize(in_data.size() * 2); // 测试单线程base16编码效率 { cout << "单线程base16开始计算" << endl; auto start = system_clock::now(); Base16Encode(in_data.data(), in_data.size(), out_data.data()); auto end = system_clock::now(); auto duration = duration_cast<milliseconds>(end - start); cout << "编码：" << in_data.size() << "字节数据花费" << double(duration.count()) << "毫秒" << endl; // cout << out_data.data() << endl; } // 测试c++11 多线程Base16编码 { cout << "c++11 多线程Base16编码 开始计算" << endl; auto start = system_clock::now(); Base16EncodeThread(in_data, out_data); auto end = system_clock::now(); auto duration = duration_cast<milliseconds>(end - start); cout << "编码：" << in_data.size() << "字节数据花费" << double(duration.count()) << "毫秒" << endl; // cout << out_data.data() << endl; } // 测试C++17 多线程base16编码 { cout << "C++17 多线程base16编码 开始计算" << endl; auto start = system_clock::now(); unsigned char* idata = in_data.data(); unsigned char* odata = out_data.data(); // #include <execution> c++17 std::for_each(std::execution::par, // 并行计算 多核 in_data.begin(), in_data.end(), [&](auto& d) // 多线程进入此函数 { char a = base16[(d >> 4)]; char b = base16[(d & 0x0F)]; int index = &d - idata; odata[index * 2] = a; odata[index * 2 + 1] = b; }); auto end = system_clock::now(); auto duration = duration_cast<milliseconds>(end - start); cout << "编码：" << in_data.size() << "字节数据花费" << double(duration.count()) << "毫秒" << endl; // cout << out_data.data() << endl; } getchar(); return 0; }c++17for_each多核运算示例编码base16clang的c++2017暂未支持execution#include <thread> #include <iostream> #include <string> #include <vector> #include <chrono> #include <execution> using namespace std; using namespace chrono; static const char base16[] = "0123456789abcdef"; void Base16Encode(const unsigned char* data, int size, unsigned char* out) { for (int i = 0; i < size; i++) { unsigned char d = data[i]; // 0000 0000 // 1234 5678 >>4 0000 1234 // 1234 5678 & 0000 1111 0000 5678 char a = base16[d >> 4]; char b = base16[d & 0x0F]; out[i * 2] = a; out[i * 2 + 1] = b; } } // C++11 多核base16编码 void Base16EncodeThread(const vector<unsigned char>& data, vector<unsigned char>& out) { int size = data.size(); int th_count = thread::hardware_concurrency(); // 系统支持的线程核心数 // 切片数据 int slice_count = size / th_count; // 余数丢弃 if (size < th_count) // 只切一片 { th_count = 1; slice_count = size; } // 准备好线程 vector<thread> ths; ths.resize(th_count); // 任务分配到各个线程 for (int i = 0; i < th_count; i++) { // 1234 5678 9abc defg hi int offset = i * slice_count; int count = slice_count; // 最后一个线程 if (th_count > 1 && i == th_count - 1) { count = slice_count + size % th_count; } // cout << offset << ":" << count << endl; ths[i] = thread(Base16Encode, data.data() + offset, count, out.data()); } // 等待所有线程处理结束 for (auto& th : ths) { th.join(); } } int main(int argc, char* argv[]) { string test_data = "测试base16编码"; unsigned char out[1024] = {0}; Base16Encode((unsigned char*)test_data.data(), test_data.size(), out); cout << "base16:" << out << endl; // 初始化测试数据 vector<unsigned char> in_data; in_data.resize(1024 * 1024 * 200); // 20M // in_data.resize(32); //20M // in_data.data(); // for (int i = 0; i < in_data.size(); i++) //{ // in_data[i] = i % 256; // } vector<unsigned char> out_data; out_data.resize(in_data.size() * 2); // 测试单线程base16编码效率 { cout << "单线程base16开始计算" << endl; auto start = system_clock::now(); Base16Encode(in_data.data(), in_data.size(), out_data.data()); auto end = system_clock::now(); auto duration = duration_cast<milliseconds>(end - start); cout << "编码：" << in_data.size() << "字节数据花费" << double(duration.count()) << "毫秒" << endl; // cout << out_data.data() << endl; } // 测试c++11 多线程Base16编码 { cout << "c++11 多线程Base16编码 开始计算" << endl; auto start = system_clock::now(); Base16EncodeThread(in_data, out_data); auto end = system_clock::now(); auto duration = duration_cast<milliseconds>(end - start); cout << "编码：" << in_data.size() << "字节数据花费" << double(duration.count()) << "毫秒" << endl; // cout << out_data.data() << endl; } // 测试C++17 多线程base16编码 { cout << "C++17 多线程base16编码 开始计算" << endl; auto start = system_clock::now(); unsigned char* idata = in_data.data(); unsigned char* odata = out_data.data(); // #include <execution> c++17 std::for_each(std::execution::par, // 并行计算 多核 in_data.begin(), in_data.end(), [&](auto& d) // 多线程进入此函数 { char a = base16[(d >> 4)]; char b = base16[(d & 0x0F)]; int index = &d - idata; odata[index * 2] = a; odata[index * 2 + 1] = b; }); auto end = system_clock::now(); auto duration = duration_cast<milliseconds>(end - start); cout << "编码：" << in_data.size() << "字节数据花费" << double(duration.count()) << "毫秒" << endl; // cout << out_data.data() << endl; } getchar(); return 0; }线程池实现步骤说明高并发运算时，线程频繁创建和销毁会有性能损耗，我们实现线程池直接在里面取即可。初始化线程池启动所有线程准备好任务处理基类和插入任务获取任务接口执行任务线程入口函数完成线程池的初始化和启动demo里仅有初始化和启动CMakeLists main.cpp XThreadPool.cpp XThreadPool.h完成线程池任务调度cv_.wait(lock, predicate)实际上类似于下面这样：while (!predicate()) { cv_.wait(lock); }在这个重载版本的wait()调用中，当线程被唤醒时（不管是因为notify_*调用还是假唤醒），它会自动检查lambda表达式。只有当lambda表达式返回true（这里是!tasks_.empty()），wait()函数才会返回。如果谓词返回false，wait()会再次阻塞线程，这实现了内部的循环检查机制，而不需要显式的while循环。 std::unique_lock<std::mutex> lock(mux_); cv_.wait(lock, [this]() { return !tasks_.empty(); });有哪些优化：针对减少虚假唤醒带来的性能影响，你可以考虑通过减少条件变量的使用或者增加批量处理来实现。由于没有具体的任务处理代码，以下是一段示例代码，说明如何在接收到一个任务时尝试处理多个任务：#include <vector> #include <mutex> #include <condition_variable> #include <functional> #include <queue> // 假设有一个任务类型 using Task = std::function<void()>; // 任务队列和同步原语 std::queue<Task> tasks_; std::mutex mux_; std::condition_variable cv_; void processTaskBatch() { // 在拿锁之前先定义要处理的任务 std::vector<Task> tasksToProcess; { std::unique_lock<std::mutex> lock(mux_); cv_.wait(lock, []() { return !tasks_.empty(); }); // 等待直到有任务 // 一次处理多个任务 while (!tasks_.empty()) { tasksToProcess.emplace_back(std::move(tasks_.front())); tasks_.pop(); // 可以根据需要在这里设置一个批处理的最大值来限制每次处理的任务数量 // 如果性能测试表明处理太多任务会导致锁竞争导致的性能瓶颈 } } // 解锁mutex // 现在已经不持有锁了，在没有阻塞其他线程的情况下处理所有任务 for (auto &task : tasksToProcess) { task(); // 执行任务 } } // 示例：工作线程函数 void workerThread() { while (true) { processTaskBatch(); } } // 假设有一个函数来添加任务到队列中 void addTask(const Task &task) { { std::unique_lock<std::mutex> lock(mux_); tasks_.push(task); } // 已经解锁 // 通知一个等待中的线程有新任务到来 cv_.notify_one(); } 这个代码示例展示了如何让工作线程不只是处理单个任务，而是尝试一次性处理多个任务，以此来减少锁的频繁请求和条件变量的多次等待。这样，拿到锁的线程会尽可能多地完成任务，减少下次需要等待和锁定的概率，同时减少了对条件变量的依赖，也就减少了虚假唤醒所造成的性能影响。对于C++线程池，有一些成熟的库和框架可供选择。其中一些包括：ThreadPool：这是一个轻量级且易于使用的开源C++11线程池库，可在GitHub上找到。Intel TBB（Threading Building Blocks）：由英特尔开发的TBB库提供了丰富的并行算法和数据结构，包括线程池。它是一个高度成熟的并行编程库，可用于各种并行化需求。PPL（Parallel Patterns Library）：PPL是Microsoft Visual C++中的库，提供了用于并行编程的线程池和并行算法。它提供了一种简单的方式来利用多核处理器的能力。以上是一些常用和成熟的C++线程池库和框架，可以根据项目需求和偏好选择合适的库。
《C++ Concurrency In Action 2ed》（第3章 共享数据）
《C++ Concurrency In Action 2ed》（第3章 共享数据）第2 章 线程管理3.1 使用互斥量当多个线程同时访问共享数据时，如果数据只读，则不会出现问题；但如果有线程需要修改共享数据，则可能引发复杂问题。1. 不变量的破坏不变量是数据结构在某一状态下的规则或约束。例如，双链表中每个节点的前后指针必须保持一致。在修改共享数据时，不变量可能会暂时被破坏，直到修改完成后才恢复稳定。如果在不变量被破坏的过程中，其他线程访问了共享数据，就可能导致错误。2. 条件竞争条件竞争是指多个线程对共享数据的访问顺序影响最终结果。良性条件竞争：线程执行顺序不同，但结果仍然可接受。恶性条件竞争：线程执行顺序导致不变量被破坏，可能导致数据损坏或程序崩溃。数据竞争是条件竞争的一种特殊形式，指多个线程同时修改一个独立对象，可能引发未定义行为。3. 恶性条件竞争的特性通常发生在对多个数据块的修改中，如双链表中两个指针的更新。错误难以复现，因为其发生概率较低，且与执行时间敏感。在系统负载高或执行次数增加时，问题出现的概率会提高。4. 避免恶性条件竞争的方法保护机制：使用某种机制（如互斥量）保护数据结构，确保其他线程无法看到不变量的中间状态。无锁编程：通过修改数据结构和不变量，使其变化过程不可分割，但这种方法复杂且容易出错。事务机制：通过事务日志记录和提交数据更新，避免条件竞争（如软件事务内存，STM）。但C++标准目前未直接支持这种方法。互斥量：C++标准库提供的最基本的方式，用于保护共享数据结构。5. 调试难点条件竞争问题可能在调试模式下消失，因为调试模式会影响程序的执行时间。通过这些方法和工具，可以有效避免恶性条件竞争，确保多线程程序的正确性和稳定性。[![top] Goto Top](#table-of-contents)3.2 使用互斥量3.2.1 互斥量核心概念互斥量（std::mutex）是C++中保护共享数据的最基础机制。它的作用是确保同一时刻只有一个线程可以访问共享数据，从而避免条件竞争和数据不一致的问题。使用方式手动锁与解锁 通过std::mutex的成员函数lock()和unlock()，可以手动对互斥量进行加锁和解锁操作，但这种方式容易出错，尤其是在代码中有多个出口（如异常处理）时，可能会导致忘记解锁。RAII机制：std::lock_guard 为了避免手动管理锁的复杂性，C++标准库提供了std::lock_guard，它是一种RAII（资源获取即初始化）机制的封装类。在构造时自动加锁，在析构时自动解锁，从而确保互斥量在任何情况下都能正确解锁。示例代码：使用std::mutex和std::lock_guard#include #include <mutex> #include <algorithm> std::list<int> some_list; // 1. 全局共享数据 std::mutex some_mutex; // 2. 保护共享数据的互斥量 // 添加元素到列表 void add_to_list(int new_value) { std::lock_guard<std::mutex> guard(some_mutex); // 3. 自动加锁 some_list.push_back(new_value); } // 检查列表中是否包含某个值 bool list_contains(int value_to_find) { std::lock_guard<std::mutex> guard(some_mutex); // 4. 自动加锁 return std::find(some_list.begin(), some_list.end(), value_to_find) != some_list.end(); }代码说明：some_list 是一个全局共享的std::list。some_mutex 是保护some_list的互斥量。在add_to_list()和list_contains()函数中，使用std::lock_guard对some_mutex进行加锁，确保线程安全。当线程访问共享数据时，互斥量会将其他线程阻塞，直到当前线程完成操作并解锁。C++17 的改进模板参数推导 在C++17中，std::lock_guard支持模板参数推导，因此可以省略类型声明，代码更加简洁。例如：std::lock_guard guard(some_mutex);std::scoped_lock C++17引入了std::scoped_lock，它是std::lock_guard的增强版，可以同时锁住多个互斥量，适用于需要同时操作多个共享数据的场景。例如：std::scoped_lock guard(some_mutex1, some_mutex2);面向对象的设计在大多数情况下，互斥量通常与需要保护的数据一起放在同一个类中，而不是定义为全局变量。这种设计符合面向对象的封装原则。示例：class ProtectedList { private: std::list<int> some_list; std::mutex some_mutex; public: void add_to_list(int new_value) { std::lock_guard<std::mutex> guard(some_mutex); some_list.push_back(new_value); } bool list_contains(int value_to_find) { std::lock_guard<std::mutex> guard(some_mutex); return std::find(some_list.begin(), some_list.end(), value_to_find) != some_list.end(); } };注意事项不要返回受保护数据的指针或引用 如果成员函数返回受保护数据的指针或引用，调用者可以绕过互斥量直接操作数据，从而破坏数据的安全性。总结std::mutex 是保护共享数据的核心机制。使用std::lock_guard可以简化锁的管理，避免忘记解锁。C++17 提供了std::scoped_lock，进一步增强了多互斥量场景的支持。面向对象的设计可以将互斥量与数据绑定在一起，方便封装和管理。3.2.2 保护共享数据核心概念使用互斥量保护共享数据时，仅仅在每个成员函数中添加一个std::lock_guard对象并不总是足够的。需要仔细检查代码，确保没有通过指针或引用绕过互斥量的保护。否则，即使看似加锁了共享数据，实际上却可能存在安全隐患。主要问题指针或引用泄露：如果某个成员函数返回了受保护数据的指针或引用，调用者可以直接操作数据，而不受互斥量的保护。这种情况下，互斥量的保护形同虚设。通过外部代码间接访问：某些成员函数可能会将受保护的数据传递给外部函数或代码（通过参数或返回值），从而导致数据暴露在未加锁的情况下。运行时参数传递问题：如果受保护数据被传递到一个用户提供的函数中，可能会导致该函数在没有互斥量保护的情况下访问数据，从而引发条件竞争。示例代码：无意中泄露受保护数据以下代码展示了一个常见的错误：将受保护数据的引用传递给外部函数，导致保护失效。#include <mutex> #include <string> #include <iostream> class some_data { int a; std::string b; public: void do_something() { std::cout << "Doing something with data" << std::endl; } }; class data_wrapper { private: some_data data; // 受保护的数据 std::mutex m; // 互斥量 public: template<typename Function> void process_data(Function func) { std::lock_guard<std::mutex> lock(m); // 加锁保护数据 func(data); // 传递数据给用户提供的函数 } }; // 恶意函数：将受保护数据的地址泄露 some_data* unprotected = nullptr; void malicious_function(some_data& protected_data) { unprotected = &protected_data; // 保存受保护数据的地址 } void example_usage() { data_wrapper x; x.process_data(malicious_function); // 传递恶意函数 unprotected->do_something(); // 在没有互斥量保护的情况下访问数据 }问题分析问题描述：process_data函数通过模板参数允许用户传入任意函数，并将受保护数据data传递给该函数。恶意函数malicious_function将data的地址保存到全局指针unprotected中。之后，程序可以通过unprotected直接访问data，而无需加锁，破坏了互斥量的保护。根本原因：process_data函数没有限制用户提供的函数func的行为。数据data在互斥量的作用范围之外被泄露。后果：其他线程可以在没有互斥量保护的情况下访问或修改data，导致数据竞争和未定义行为。解决方案不要返回指针或引用：确保成员函数不会将受保护数据的指针或引用通过返回值或参数传递给调用者。例如：// 错误：返回受保护数据的引用 some_data& get_data() { std::lock_guard<std::mutex> lock(m); return data; // 这样调用者可以直接操作受保护数据 }限制外部访问：避免将受保护数据直接暴露给外部函数或代码。如果必须传递数据，考虑使用只读的副本或深拷贝。封装逻辑：将所有对受保护数据的操作封装在类的成员函数中，避免外部代码直接操作数据。示例代码：改进后的设计以下代码展示了如何避免泄露受保护数据的引用或指针：#include <mutex> #include <string> #include <iostream> class some_data { int a; std::string b; public: void do_something() const { std::cout << "Doing something with data" << std::endl; } }; class data_wrapper { private: some_data data; // 受保护的数据 std::mutex m; // 互斥量 public: void safely_process_data() { std::lock_guard<std::mutex> lock(m); // 加锁保护数据 data.do_something(); // 在锁的保护下操作数据 } };改进点：数据data的操作被封装在data_wrapper的成员函数中。外部代码无法直接访问data，只能通过data_wrapper提供的接口进行操作。总结使用互斥量保护数据时，需要特别注意不要泄露受保护数据的指针或引用。确保所有对受保护数据的访问都在互斥量的作用范围内。封装数据访问逻辑，将互斥量与数据绑定在同一个类中，以减少错误发生的可能性。[![top] Goto Top](#table-of-contents)
《C++ Concurrency In Action 2ed》（第2章 线程管理）
《C++ Concurrency In Action 2ed》（第2章 线程管理）2.1 线程的基本操作线程的基本概念每个C++程序至少有一个主线程，负责执行main()函数。新线程可以通过std::thread对象启动，线程的生命周期由其关联的函数或任务决定。当线程的任务函数执行完毕，线程会自动退出。线程的启动使用std::thread对象启动线程：void do_some_work(); std::thread my_thread(do_some_work);线程启动后会执行do_some_work()。也可以使用可调用对象（如类的operator()）启动线程：class background_task { public: void operator()() const { do_something(); do_something_else(); } }; background_task f; std::thread my_thread(f);注意避免“最令人头痛的语法解析问题”（Most Vexing Parse）：std::thread my_thread(background_task()); // 被解析为函数声明解决方法：使用额外的括号：std::thread my_thread((background_task()));使用统一初始化语法：std::thread my_thread{background_task()};使用Lambda表达式：std::thread my_thread([] { do_something(); do_something_else(); });线程的等待与分离等待线程完成 (join)使用join()可以等待线程完成：std::thread t(do_some_work); t.join(); // 阻塞当前线程，直到`t`完成join()会清理线程相关的资源，调用后std::thread对象不再与线程关联。一个线程只能join一次。分离线程 (detach)使用detach()将线程与std::thread对象分离，线程会在后台运行：std::thread t(do_background_work); t.detach();分离的线程无法再通过std::thread对象控制，线程的资源会在其完成后自动回收。注意在std::thread对象销毁前，必须对其调用join()或detach()，否则会导致程序终止（调用std::terminate()）。线程生命周期管理避免局部变量的引用问题如果线程函数中使用了局部变量的引用，而线程未完成时局部变量已经销毁，会导致未定义行为：struct func { int& i; func(int& i_) : i(i_) {} void operator()() { for (unsigned j = 0; j < 1000000; ++j) { do_something(i); // 可能访问已销毁的局部变量 } } }; void oops() { int some_local_state = 0; func my_func(some_local_state); std::thread my_thread(my_func); my_thread.detach(); // 分离线程 } // some_local_state 被销毁，但线程仍在运行解决方法：将局部变量复制到线程中，而不是传引用。使用join()确保线程结束后再销毁局部变量。使用RAII管理线程RAII（资源获取即初始化）模式可以确保线程在异常或函数退出时正确管理：class thread_guard { std::thread& t; public: explicit thread_guard(std::thread& t_) : t(t_) {} ~thread_guard() { if (t.joinable()) { t.join(); } } thread_guard(const thread_guard&) = delete; thread_guard& operator=(const thread_guard&) = delete; }; void f() { int some_local_state = 0; func my_func(some_local_state); std::thread t(my_func); thread_guard g(t); // RAII管理线程 do_something_in_current_thread(); } // thread_guard析构时会调用t.join()异常安全在异常处理中也要确保线程正确join：void f() { int some_local_state = 0; func my_func(some_local_state); std::thread t(my_func); try { do_something_in_current_thread(); } catch (...) { t.join(); // 确保线程结束 throw; } t.join(); }后台线程（守护线程）使用detach()可以创建后台线程（守护线程），这种线程通常用于执行长期任务或后台监控：void edit_document(std::string const& filename) { open_document_and_display_gui(filename); while (!done_editing()) { user_command cmd = get_user_input(); if (cmd.type == open_new_document) { std::string const new_name = get_filename_from_user(); std::thread t(edit_document, new_name); // 启动新线程 t.detach(); // 分离线程 } else { process_user_input(cmd); } } }适用场景：长期运行的任务（如文件监控、缓存清理等）。不需要主线程直接交互的任务。总结使用std::thread可以方便地启动线程，但需要注意线程的生命周期管理。等待线程：使用join()确保线程完成后释放资源。使用RAII模式或异常处理机制避免线程未正确结束。分离线程：使用detach()让线程在后台运行，但要确保线程的任务不会依赖已销毁的资源。避免问题：避免线程函数中持有局部变量的引用。谨慎处理异常，确保线程安全。[![top] Goto Top](#table-of-contents)2.2 传递参数在C++中，使用 std::thread 创建线程时，可以为线程函数传递参数。传递参数的方式很灵活，但需要注意一些细节，避免潜在的陷阱。基本用法在创建线程时，可以将参数直接传递给 std::thread 的构造函数。参数会被拷贝到新线程的上下文中。void f(int i, std::string const& s); // 一个线程函数 std::thread t(f, 3, "hello"); // 创建线程，传递参数3和"hello"参数拷贝：在例子中，std::thread 将参数 3 和 "hello" 传递给函数 f。注意：std::thread 会将参数拷贝到新线程中，即使函数参数是引用类型。字符串字面量的隐式转换问题如果将字符串字面量（如 "hello"）传递到一个期望 std::string 类型的参数中， std::thread 会隐式地将其转换为 std::string`。但需要小心动态分配的临时对象。错误示例：void f(int i, std::string const& s); void oops(int some_param) { char buffer[1024]; // 临时字符数组 sprintf(buffer, "%i", some_param); // 格式化字符串 std::thread t(f, 3, buffer); // 错误！buffer可能在线程运行前被销毁 t.detach(); }问题：buffer 是局部变量，在线程启动前可能已经销毁。线程可能访问到无效的指针，导致未定义行为。解决办法：在传递参数前，显式地将 buffer 转换为 std::string，以确保线程拥有独立的拷贝。std::thread t(f, 3, std::string(buffer)); // 显式转换为std::string传递引用参数如果线程函数需要引用参数，直接传递会导致 std::thread 拷贝参数，而不是传递引用。这会导致编译错误。错误示例：void update_data(int& data); // 线程函数需要引用参数 int value = 42; std::thread t(update_data, value); // 错误！期望引用，但传递了拷贝解决办法：使用 std::ref 包装引用参数，显式地告诉 std::thread 传递引用。std::thread t(update_data, std::ref(value)); // 使用std::ref传递引用std::ref 是一个辅助函数，用于创建 std::reference_wrapper 对象，保证参数以引用的形式传递。传递成员函数指针可以将类的成员函数作为线程函数传递，但需要同时提供该成员函数所属的对象。示例：class X { public: void do_work(int value) { // 成员函数 } }; X x; std::thread t(&X::do_work, &x, 42); // 调用x.do_work(42)第一个参数是成员函数指针（&X::do_work）。第二个参数是对象指针（&x）。后续参数是传递给成员函数的参数（42）。传递只支持移动的对象有些对象类型（如 std::unique_ptr）只能通过移动语义传递，而不能拷贝。对于这些类型，需要显式地使用 std::move。示例：void process(std::unique_ptr<int> p); std::unique_ptr<int> ptr(new int(42)); std::thread t(process, std::move(ptr)); // 使用std::move转移所有权std::move 将 ptr 的所有权转移到线程中。线程函数 process 将接管 std::unique_ptr 的所有权。总结参数传递方式：默认情况下，std::thread 会拷贝参数到新线程中。使用 std::ref 可以传递引用。使用 std::move 可以传递只支持移动的对象。注意事项：避免传递局部变量的指针或引用，防止线程访问到销毁的变量。显式转换动态分配的临时对象，确保线程拥有独立的拷贝。[![top] Goto Top](#table-of-contents)2.3 转移所有权背景在C++中，std::thread 对象拥有线程的所有权。这意味着每个 std::thread 实例负责管理一个线程的生命周期。为了确保线程的所有权是唯一的，std::thread 对象是不可复制的（non-copyable），但它是可移动的（movable）。这允许我们在程序中将线程的所有权从一个 std::thread 对象转移到另一个对象。时需要转移所有权？转移线程所有权的场景包括：将新创建的线程返回给调用者：一个函数创建线程并将其返回给调用者，而不是自己管理线程。将线程的管理权交给另一个函数：一个函数创建线程后，将其所有权转交给另一个函数进行管理。容器管理线程：当需要将多个线程存储在容器（如 std::vector）中时，线程对象需要支持移动操作。示例代码以下是一个展示如何转移线程所有权的代码示例：void some_function(); void some_other_function(); std::thread t1(some_function); // 创建线程 t1 std::thread t2 = std::move(t1); // 将 t1 的线程所有权转移给 t2 t1 = std::thread(some_other_function); // t1 现在管理一个新线程 std::thread t3; // 创建一个空的线程对象 t3 = std::move(t2); // 将 t2 的线程所有权转移给 t3 t1 = std::move(t3); // 将 t3 的线程所有权转移回 t1代码解析创建线程并转移所有权std::thread t1(some_function);：创建一个线程 t1，它运行 some_function。std::thread t2 = std::move(t1);：将 t1 的线程所有权转移给 t2。此时，t1 不再管理任何线程。重新赋值线程t1 = std::thread(some_other_function);：将 t1 重新赋值为一个新线程，运行 some_other_function。空线程对象std::thread t3;：创建一个空的线程对象 t3。t3 = std::move(t2);：将 t2 的线程所有权转移给 t3。防止未管理线程t1 = std::move(t3);：将 t3 的线程所有权转移回 t1。注意：如果 t1 在转移之前已经管理了一个线程，而该线程未被显式 join() 或 detach()，则会导致程序异常终止（调用 std::terminate()）。注意事项线程对象的生命周期在 std::thread 的生命周期结束时，必须确保线程已被 join() 或 detach()，否则程序会调用 std::terminate()。如果需要转移所有权，务必在转移前处理旧的线程对象。std::move 的使用转移所有权时，必须使用 std::move()，因为线程对象是不可复制的。线程对象的空状态转移所有权后，原线程对象进入“空状态”（即不再管理任何线程）。可以通过调用 joinable() 检查线程对象是否仍然管理线程。函数中转移线程所有权线程所有权的转移也可以通过函数返回值实现。例如：std::thread create_thread() { void some_function(); return std::thread(some_function); // 返回一个线程对象，调用者接管所有权 } void use_thread() { std::thread t = create_thread(); // 接管线程所有权 t.join(); // 确保线程完成 }在这个例子中，create_thread() 创建一个线程并将其返回，调用者负责管理线程的生命周期。当所有权可以在函数内部传递，就允许std::thread实例作为参数进行传递，代码如下：void f(std::thread t); void g() { void some_function(); f(std::thread(some_function)); std::thread t(some_function); f(std::move(t)); }封装线程所有权可以通过封装来简化线程所有权的管理，例如使用 scoped_thread：class scoped_thread { std::thread t; public: explicit scoped_thread(std::thread t_) : t(std::move(t_)) { if (!t.joinable()) { throw std::logic_error("No thread"); } } ~scoped_thread() { t.join(); // 确保线程完成 } scoped_thread(scoped_thread const&) = delete; scoped_thread& operator=(scoped_thread const&) = delete; };此类会在析构时自动调用 join()，确保线程被正确管理。量产线程void do_work(unsigned id); void f() { std::vector<std::thread> threads; for (unsigned i = 0; i < 20; ++i) { threads.emplace_back(do_work,i); // 产生线程 } for (auto& entry : threads) // 对每个线程调用 join() entry.join(); }我们有时需要线程去分割一个算法的工作总量，所以在算法结束的之前，所有的线程必须结束。代码2.8中线程所做的工作都是独立的，并且结果仅会受到共享数据的影响。如果f()有返回值，这个返回值就依赖于线程得到的结果。写入返回值之前，程序会检查使用共享数据的线程是否终止。结果在不同线程中转移的方案，会在第4章中再次讨论。将std::thread放入std::vector是向线程自动化管理迈出的第一步：并非为这些线程创建独立的变量，而是把它们当做一个组。创建一组线程(数量在运行时确定)，而非代码2.8那样创建固定数量的线程。总结C++ 中的 std::thread 是 不可复制 的，但可以通过 std::move 转移所有权。转移线程所有权时，确保线程对象未管理线程，或已正确处理（join() 或 detach()）。使用封装类（如 scoped_thread）可以简化线程管理，避免资源泄漏。[![top] Goto Top](#table-of-contents)2.4 确定线程数量这个例子主要是实现一个并行版本的std::accumulate，通过多线程加速累加操作。背景知识std::accumulate：是标准库中的一个函数，用于对一个范围内的元素进行累加。它是串行的（单线程），所以在处理大量数据时性能会受到限制。并行化的目标：将数据分成多个块，每个线程处理一部分数据，最后汇总所有线程的结果。通过并行化，充分利用多核 CPU 的性能。std::accumulate 是 C++ 标准库 <numeric> 头文件中提供的一个函数模板，用于对范围内的元素进行累积操作（即求和、求积或其他自定义操作）。它是一个非常灵活且强大的工具。基本语法#include <numeric> template<class InputIterator, class T> T accumulate(InputIterator first, InputIterator last, T init); template<class InputIterator, class T, class BinaryOperation> T accumulate(InputIterator first, InputIterator last, T init, BinaryOperation op);参数说明first 和 last:表示输入范围的起始迭代器和结束迭代器，范围是 [first, last)，即包含 first 指向的元素，但不包含 last。init:累积操作的初始值，累积操作从这个值开始。op（可选）:自定义的二元操作函数或函数对象。默认情况下是加法操作。返回值返回累积操作的结果，类型与 init 相同。使用示例1. 简单求和默认情况下，std::accumulate 使用加法操作：#include <iostream> #include <numeric> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; int sum = std::accumulate(vec.begin(), vec.end(), 0); // 初始值为 0 std::cout << "Sum: " << sum << std::endl; // 输出 15 return 0; }2. 求积通过自定义操作符可以实现其他累积操作，例如求积：#include <iostream> #include <numeric> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; int product = std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>()); std::cout << "Product: " << product << std::endl; // 输出 120 return 0; }3. 字符串连接std::accumulate 不仅可以用于数值，还可以用于字符串等其他类型：#include <iostream> #include <numeric> #include <vector> #include <string> int main() { std::vector<std::string> words = {"Hello", " ", "World", "!"}; std::string result = std::accumulate(words.begin(), words.end(), std::string("")); std::cout << "Result: " << result << std::endl; // 输出 "Hello World!" return 0; }4. 自定义操作可以使用自定义的二元函数：#include <iostream> #include <numeric> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; int custom = std::accumulate(vec.begin(), vec.end(), 0, [](int a, int b) { return a + b * 2; // 每个元素乘以 2 后再累加 }); std::cout << "Custom result: " << custom << std::endl; // 输出 30 return 0; }2. 代码结构解析完整代码如下：template<typename Iterator,typename T> struct accumulate_block { void operator()(Iterator first,Iterator last,T& result) { result=std::accumulate(first,last,result); } }; template<typename Iterator,typename T> T parallel_accumulate(Iterator first,Iterator last,T init) { unsigned long const length=std::distance(first,last); if(!length) // 1 return init; unsigned long const min_per_thread=25; unsigned long const max_threads= (length+min_per_thread-1)/min_per_thread; // 2 unsigned long const hardware_threads= std::thread::hardware_concurrency(); unsigned long const num_threads= // 3 std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); unsigned long const block_size=length/num_threads; // 4 std::vector<T> results(num_threads); std::vector<std::thread> threads(num_threads-1); // 5 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); // 6 threads[i]=std::thread( // 7 accumulate_block<Iterator,T>(), block_start,block_end,std::ref(results[i])); block_start=block_end; // 8 } accumulate_block<Iterator,T>()( block_start,last,results[num_threads-1]); // 9 for (auto& entry : threads) entry.join(); // 10 return std::accumulate(results.begin(),results.end(),init); // 11 }以下是代码的核心部分及其功能解释：(1) accumulate_block 结构体template<typename Iterator, typename T> struct accumulate_block { void operator()(Iterator first, Iterator last, T& result) { result = std::accumulate(first, last, result); } };功能：这是一个可调用对象（仿函数），用于对指定范围的元素进行累加。参数：first 和 last：范围的起始和结束迭代器。result：存储累加结果的变量。实现：调用标准库的 std::accumulate，对范围 [first, last) 内的元素求和，并将结果存储到 result 中。(2) parallel_accumulate 函数这是整个并行化逻辑的核心部分。a. 输入检查unsigned long const length = std::distance(first, last); if (!length) // 如果范围为空 return init;功能：检查输入范围是否为空。如果为空，直接返回初始值 init。b. 计算线程数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：根据数据量和 min_per_thread 计算出理论上的最大线程数。hardware_threads：获取硬件支持的并发线程数。如果无法获取（返回值为 0），默认使用 2。num_threads：取硬件线程数和 max_threads 的较小值，确保线程数合理。在这段代码中，使用 unsigned long 而不是 int 类型的原因主要与以下几个方面有关：避免负值unsigned long 是无符号类型，而 int 是有符号类型。在这段代码中，min_per_thread 和 max_threads 都是表示线程数量或长度的值，这些值本质上是非负的。如果使用 int，可能会引入负数的风险（例如，由于计算错误或溢出），而这些值本身不应该为负数。使用 unsigned long 可以明确表达这些值只能是非负的，避免负值带来的逻辑问题。防止溢出unsigned long 通常比 int 能表示更大的范围（具体范围取决于平台和编译器）。例如：在 32 位系统上，int 通常是 32 位，范围为 -2,147,483,648 到 2,147,483,647。而 unsigned long 通常是 32 位或更大，范围为 0 到 4,294,967,295（或更大）。如果 length 是一个非常大的值（例如表示一个超长数组的长度），使用 int 可能会导致溢出，而 unsigned long 能更安全地处理大范围的值。与硬件线程数量的兼容性std::thread::hardware_concurrency() 返回值的类型是 unsigned int，表示硬件支持的并发线程数。为了兼容性，代码中使用了一个更宽泛的类型 unsigned long，以确保在不同平台上不会因为类型不匹配而导致问题。表达意图使用 unsigned long 明确表达了这些值是非负的，并且可能需要处理较大的范围。这是一种编码风格上的选择，旨在提高代码的可读性和可维护性。性能影响在大多数情况下，unsigned long 和 int 的性能差异可以忽略不计。选择 unsigned long 是为了更好地适应可能的大范围值，而不是因为性能原因。c. 划分任务块unsigned long const block_size = length / num_threads; std::vector<T> results(num_threads); std::vector<std::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_block<Iterator, T>(), block_start, block_end, std::ref(results[i])); block_start = block_end; }核心逻辑：每个线程处理的任务块大小：block_size = length / num_threads。存储中间结果：用 std::vector<T> results 存储每个线程的计算结果。存储线程对象：用 std::vector<std::thread> threads 保存所有线程。任务分配：使用 std::advance 将迭代器 block_start 移动到当前块的末尾。创建线程，调用 accumulate_block 对当前块进行累加。将累加结果存储到 results[i] 中。d. 处理最后一个任务块accumulate_block<Iterator, T>()( block_start, last, results[num_threads - 1]);功能：主线程处理最后一个任务块。原因：线程数比任务块数少 1，因此最后一个任务块由主线程完成。e. 等待线程完成for (auto& entry : threads) entry.join();功能：等待所有线程完成任务（调用 join）。f. 汇总结果return std::accumulate(results.begin(), results.end(), init);功能：对 results 中的中间结果进行累加，得到最终结果。3. 总结代码流程检查输入范围是否为空。根据数据量和硬件支持，计算需要启动的线程数。将数据划分为多个任务块。创建线程并分配任务块，计算中间结果。主线程处理最后一个任务块。等待所有线程完成。汇总中间结果，返回最终结果。4. 注意事项线程数的选择：线程数过多会导致上下文切换开销，性能反而下降。线程数过少可能无法充分利用多核 CPU 的性能。结果的准确性：如果 T 是浮点类型（如 float 或 double），并行化可能导致结果与串行版本不同（因为加法不满足结合律）。迭代器要求：必须是前向迭代器（如 std::vector 的迭代器）。5. 扩展在 C++17 中，可以直接使用 std::reduce 实现并行累加，它是 std::accumulate 的并行版本，避免了手动管理线程的复杂性。[![top] Goto Top](#table-of-contents)2.5 线程标识1. 什么是线程标识？线程标识是用来唯一标识一个线程的对象，其类型为std::thread::id。它是C++标准线程库中用于区分不同线程的工具。线程标识的类型：std::thread::id。默认值：如果线程标识没有关联到任何线程，则它会有一个默认构造值，表示“无线程”。2. 获取线程标识的两种方式通过std::thread对象获取：调用std::thread对象的成员函数get_id()可以获取该线程的标识。如果该std::thread对象没有关联到任何线程，get_id()将返回默认的“无线程”值。std::thread t([]{ /*线程任务*/ }); std::cout << t.get_id(); // 输出线程t的ID t.join();通过当前线程获取：调用std::this_thread::get_id()可以获取当前线程的标识。这个函数定义在<thread>头文件中。std::cout << std::this_thread::get_id(); // 输出当前线程的ID3. 线程标识的特性可复制和比较：std::thread::id对象可以自由地拷贝和比较。如果两个std::thread::id对象相等，则它们要么代表同一个线程，要么都表示“无线程”。如果不相等，则说明它们是不同的线程，或者一个有线程，一个没有线程。排序和哈希支持：std::thread::id支持比较操作（如<、>），这意味着可以作为排序容器的键值。标准库还提供了std::hash<std::thread::id>，因此可以将std::thread::id作为无序容器（如std::unordered_map）的键值。4. 使用场景4.1 检测线程是否需要执行某些操作在多线程程序中，可以通过线程标识来区分主线程和其他线程，从而决定是否执行特定操作。例如：std::thread::id master_thread; void some_core_part_of_algorithm() { if (std::this_thread::get_id() == master_thread) { do_master_thread_work(); // 主线程的特殊工作 } do_common_work(); // 所有线程的通用工作 }4.2 线程信息存储线程标识可以用作容器的键值，用来存储线程相关的信息。例如：用std::thread::id作为键值存储线程的状态。在多线程间传递信息时，用线程标识来区分不同的线程。4.3 输出线程标识可以通过std::cout输出线程标识的值，用于调试或日志记录：std::cout << std::this_thread::get_id();注意：线程标识的输出结果依赖于具体的实现，C++标准只要求相同的线程必须有相同的输出。5. 实际应用线程区分：在多线程程序中区分主线程和工作线程。线程数据管理：用线程标识作为键值，存储线程相关的数据或状态。调试和日志记录：通过线程标识帮助定位问题。总结std::thread::id是C++中用于标识线程的工具。可以通过std::thread::get_id()或std::this_thread::get_id()获取。具有可比较、可排序、支持哈希等特性。常用于线程区分、数据存储和调试。[![top] Goto Top](#table-of-contents)