烈性果汁 - MultyThread
type: channel
link: https://nhyilin.cn/tag/MultyThread/
items: 多线程简易笔记
items: 《C++ Concurrency In Action 2ed》（第3章 共享数据）
items: 《C++ Concurrency In Action 2ed》（第2章 线程管理）

多线程简易笔记
type: item
date: 2025-02-11T13:25:00+08:00
description: 作为开发过程中手册浏览。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()，就会导致主线程也无法继续…

《C++ Concurrency In Action 2ed》（第3章 共享数据）
type: item
date: 2025-02-06T15:50:00+08:00
description: 《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_m…

《C++ Concurrency In Action 2ed》（第2章 线程管理）
type: item
date: 2025-02-06T14:19:00+08:00
description: 《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：voi…