# C++并发编程 ## 并发基础 ### 1. std::thread | default (1) | thread () noexcept; | | ------------------ | ------------------------------------------------------------ | | initialization (2) | template explicit thread (Fn&& fn, Args&&... args); | | copy [deleted] (3) | thread (const thread&) = delete; | | move (4) | thread (thread&& x) noexcept; | 1、默认构造函数,创建一个空的 thread 执行对象。 2、初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。 3、拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。 4、move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。 **注意:** 可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached. std::thread在使用上容易出错,即std::thread对象在线程函数运行期间必须是有效的。什么意思呢? ``` #include #include void threadproc() { while(true) { std::cout << "I am New Thread!" << std::endl; } } void func() { std::thread t(threadproc); } int main() { func(); while(true) {} //让主线程不要退出 return 0; } ``` 以上代码再main函数中调用了func函数,在func函数中创建了一个线程,乍一看好像没有什么问题,但在实际运行是会崩溃。 **崩溃的原因:** **在func函数调用结束后,func中局部变量t(线程对象)被销毁,而此时线程函数仍在运行**。所以在使用std::thread类时,必须保证线程函数运行期间其线程对象有效。 std::thread对象提供了一个detach方法,通过这个方法可以让线程对象与线程函数脱离关系,这样即使线程对象被销毁,也不影响线程函数的运行。 只需要在func函数中调用detach方法即可,代码如下: ``` // 其他代码保持不变 void func() { std::thread t(threadproc); t.detach(); } ``` ### 2. lock_guard lock_guard是一个互斥量包装程序,它提供了一种方便的RAII(Resource acquisition is initialization )风格的机制来在作用域块的持续时间内拥有一个互斥量。 创建lockguard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lockguard对象的作用域时,lock_guard析构并释放互斥量。 **它的特点如下:** 1. 创建即加锁,作用域结束自动析构并解锁,无需手工解锁 2. 不能中途解锁,必须等作用域结束才解锁 3. 不能复制 ### 3. unique_lock unique_lock是一个通用的互斥量锁定包装器,它允许延迟锁定,限时深度锁定,递归锁定,锁定所有权的转移以及与条件变量一起使用。 简单地讲,uniquelock 是 lockguard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。 **特点如下:** 1. 创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定 2. 可以随时加锁解锁 3. 作用域规则同 lock_grard,析构时自动释放锁 4. 不可复制,可移动 5. 条件变量需要该类型的锁作为参数(此时必须使用unique_lock) ## 如何在C++中创建和管理线程?(考点:创建线程)【简单】 #### 如何在C++中创建和管理线程? C++11 引入了 `` 头文件,提供了标准化的线程操作接口,使得多线程编程更加便捷和可移植。 1. **创建线程** 使用 `std::thread` 创建一个线程,可以传递函数、函数指针、lambda 表达式等作为线程的执行内容。 2. 线程的生命周期管理 - **`join()`**: 主线程调用 `join()` 来等待新线程的结束。`join()` 确保主线程等待子线程完成,避免主线程提前结束,导致子线程被销毁。 - **`detach()`**: 如果不需要等待线程结束,而是希望让线程独立运行,可以调用 `detach()`。被 `detach` 的线程会在后台继续执行,主线程结束后,`std::thread` 会自动清理线程资源。 3. 线程的返回值 ``` std::thread ``` 本身不支持线程返回值。如果需要线程返回结果,可以使用 ``` std::future ``` 和 ``` std::promise ``` 来异步获取结果。 示例:使用 `std::async` 获取线程返回值 ``` std::async ``` 可以方便地创建一个线程并返回一个 ``` std::future ``` ,通过 ``` future ``` 获取线程的结果。 ``` #include #include int add(int a, int b) { return a + b; } int main() { // 使用 std::async 启动线程并返回 std::future std::future result = std::async(std::launch::async, add, 5, 3); // 获取线程结果 std::cout << "Result: " << result.get() << std::endl; return 0; } ``` 在这个例子中, ``` std::async ``` 创建了一个新的线程并执行 ``` add ``` 函数,返回一个 ``` std::future ``` 对象。调用 ``` result.get() ``` 会阻塞主线程直到获取到线程的返回值。 > 在C++中,我们通过`std::thread`类来创建线程。创建时需向其构造函数传递可调用对象——可以是普通函数、Lambda表达式或类成员函数。对于成员函数,需要同时传递对象指针和函数参数。线程启动后,必须通过`join()`或`detach()`进行生命周期管理:`join()`会确保主线程等待子线程完成,避免主线程提前结束,导致子线程被销毁;`detach()`则让线程在后台独立运行,主线程结束后,`std::thread` 会自动清理线程资源。这里有个关键点:如果未调用`join()`或`detach()`,`std::thread`析构时会强制终止程序,这是C++防止线程泄漏的安全机制。 > 在现代C++20中,更推荐使用`std::jthread`,它在析构时自动调用`join()`,彻底避免手动管理的风险。多线程环境下,共享数据必须通过同步机制保护——最常用的是`std::mutex`配合`std::lock_guard`实现RAII式加锁,或者使用`std::atomic`进行无锁编程。对于简单异步任务,直接用`std::async`封装比手动管理线程更安全高效,它能自动处理结果返回和异常传递。 实际开发中,我会通过`std::thread::hardware_concurrency()`获取硬件支持的并发数来优化线程数量,复杂场景则采用线程池模式避免频繁创建销毁。总的来说,C++线程管理的核心在于严格遵循"创建-同步-回收"的生命周期,优先选用现代工具如`jthread`和`async`,并始终确保共享数据的线程安全。 **补充点(若面试官追问)**: - **为什么`detach()`后无法控制线程?** 分离的线程由C++运行时托管,无法再获取其状态或结果,也不保证生命周期。 - **何时该用`join()` vs `detach()`?** `join()`用于需要等待结果的场景;`detach()`适用于后台监控、日志写入等无需交互的任务。 - **如何避免死锁?** 按固定顺序上锁,或用`std::lock(mtx1,mtx2)`同时锁定多个互斥量,并配合`lock_guard`的`adopt_lock`标签。 ## 请解释C++11中的thread、mutex和lock_guard。 (考点:多线程编程)【中等】 ### 简要回答 在C++11中: - `std::thread`:用于创建和管理线程。 - `std::mutex`:互斥量,用于保护共享数据,防止多个线程同时访问。 - `std::lock_guard`:是一个作用域锁,用于简化`mutex`的锁定和解锁操作。 ### 详细回答 **std::thread**: - `std::thread`类表示单个执行线程。它可以被用来包装函数或者可调用对象,从而在单独的线程中执行。 - 创建线程时,可以传递函数和参数,线程启动后会执行该函数。 - 线程可以通过`join()`方法等待其完成,或者通过`detach()`方法使其独立运行。 示例代码: cpp #include #include void func() { std::cout << "Hello from thread!" << std::endl; } int main() { std::thread t(func); t.join(); return 0; } **std::mutex**: - `std::mutex`是互斥量,提供基本的线程同步机制,用于保护共享数据。 - 当一个线程锁定了一个互斥量,其他尝试锁定同一个互斥量的线程将会阻塞,直到互斥量被解锁。 - `std::mutex`提供了`lock()`、`unlock()`和`try_lock()`方法来控制互斥量的状态。 示例代码: cpp #include #include #include std::mutex mtx; // 创建互斥量 void print_block(int n, char c) { mtx.lock(); // 锁定互斥量 for (int i = 0; i < n; ++i) { std::cout << c; } std::cout << '\n'; mtx.unlock(); // 解锁互斥量 } int main() { std::thread t1(print_block, 50, '*'); std::thread t2(print_block, 50, '$'); t1.join(); t2.join(); return 0; } **std::lock_guard**: - `std::lock_guard`是一个作用域锁,它在构造时自动锁定互斥量,并在析构时自动解锁互斥量。 - 它简化了互斥量的管理,避免了忘记解锁互斥量导致的死锁问题。 示例代码: cpp #include #include #include std::mutex mtx; // 创建互斥量 void print_block(int n, char c) { std::lock_guard[std::mutex](https://notes.kamacoder.com/bagu/2KDIUWwN9d2) guard(mtx); // 自动锁定和解锁 for (int i = 0; i < n; ++i) { std::cout << c; } std::cout << '\n'; } int main() { std::thread t1(print_block, 50, '*'); std::thread t2(print_block, 50, '$'); t1.join(); t2.join(); return 0; } ### 知识拓展 - **死锁**:当两个或多个线程永久地等待对方持有的资源时,就会发生死锁。使用`std::lock_guard`而不是手动锁定和解锁互斥量可以减少死锁的风险。 - **RAII**:`std::lock_guard`遵循RAII(Resource Acquisition Is Initialization)原则,即在对象构造时获取资源,在对象析构时释放资源,这是C++管理资源的一种常见方式。 - **其他同步机制**:除了`std::mutex`和`std::lock_guard`,C++11还提供了其他同步机制,如`std::unique_lock`(提供更多灵活性),`std::condition_variable`(用于线程间的条件同步)等。 - **原子操作**:在多线程编程中,有时可以使用原子操作来避免使用互斥量,从而提高性能。 - **锁策略**:`std::lock_guard`是独占锁,`std::shared_mutex`提供了共享锁,允许多个线程读取共享数据,但只允许一个线程写入。 - **线程局部存储**:`thread_local`关键字可以声明线程局部存储的变量,这些变量在每个线程中都有独立的实例,互不干扰。 ## C++中lock_guard和unique_lock的区别?(考点:锁管理机制)【中等】 lock_guard是自动lock和unlock,unique_lock支持手动,并且有多种方式,比如条件变量。 lock_guard是基于互斥锁std::mutex实现的,unique_lock是基于std::unique_lock实现的。 lock_guard不可以移动,unique_lock可以移动。 ## 如何理解C++中的atomic?(考点:原子操作)【中等】 原子变量,可以保证变量的赋值,修改是原子性的,要不全部完成,要么完全不操作 ## 聊一聊C++中的多线程同步机制。(考点:多线程同步)【中等】 #### 简要回答 C++中的多线程同步机制用于协调多个线程对共享资源的访问,避免数据竞争和不一致。常见的同步机制包括互斥锁(`std::mutex`)、条件变量(`std::condition_variable`)、原子操作(`std::atomic`)和信号量(`std::counting_semaphore`)。互斥锁用于保护临界区,条件变量用于线程间通信,原子操作确保操作的不可分割性,信号量用于控制资源访问数量。 #### 详细回答 1. **互斥锁(`std::mutex`)**: - 互斥锁是最基本的同步机制,用于保护临界区,确保同一时间只有一个线程可以访问共享资源。 - 使用`lock()`和`unlock()`方法手动控制锁的获取和释放,也可以使用`std::lock_guard`或`std::unique_lock`进行自动管理。 - 示例: ``` std::mutex mtx; void critical_section() { std::lock_guard lock(mtx); // 访问共享资源 } ``` 2. **条件变量(`std::condition_variable`)**: - 条件变量用于线程间的通信,允许线程等待某个条件成立后再继续执行。 - 通常与互斥锁一起使用,通过`wait()`、`notify_one()`和`notify_all()`方法实现线程的等待和唤醒。 - 示例: ``` std::mutex mtx; std::condition_variable cv; bool ready = false; void wait_for_ready() { std::unique_lock lock(mtx); cv.wait(lock, []{ return ready; }); // 条件满足后继续执行 } void set_ready() { std::lock_guard lock(mtx); ready = true; cv.notify_all(); } ``` 3. **原子操作(`std::atomic`)**: - 原子操作确保对共享变量的操作是不可分割的,避免了数据竞争。 - 适用于简单的计数器或标志位等场景。 - 示例: ``` std::atomic counter(0); void increment() { counter.fetch_add(1); } ``` 4. **信号量(`std::counting_semaphore`)**: - 信号量用于控制对共享资源的访问数量,允许多个线程同时访问有限数量的资源。 - 通过`acquire()`和`release()`方法控制资源的获取和释放。 - 示例: ``` std::counting_semaphore<10> semaphore(10); void access_resource() { semaphore.acquire(); // 访问资源 semaphore.release(); } ``` ### 知识拓展 - **死锁**:当多个线程互相等待对方释放锁时,可能导致死锁。避免死锁的策略包括按固定顺序获取锁、使用`std::lock`一次性获取多个锁、以及设置超时机制。 - **读写锁(`std::shared_mutex`)**:允许多个读线程同时访问共享资源,但写线程独占访问。适用于读多写少的场景。 - **无锁编程**:通过原子操作和内存屏障实现无锁数据结构,如无锁队列,适用于高性能场景,但实现复杂且容易出错。 - **内存模型**:C++11引入了内存模型,定义了多线程环境下的内存操作顺序,帮助开发者理解并控制多线程程序的执行顺序。 ## C++如何实现线程池?给出大体的思路?(考点:线程池的实现)【困难】 一个任务队列 一组工作线程 从任务队列中取任务 同步机制C++并发编程 ## 并发基础 ### 1. std::thread | default (1) | thread () noexcept; | | ------------------ | ------------------------------------------------------------ | | initialization (2) | template explicit thread (Fn&& fn, Args&&... args); | | copy [deleted] (3) | thread (const thread&) = delete; | | move (4) | thread (thread&& x) noexcept; | 1、默认构造函数,创建一个空的 thread 执行对象。 2、初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。 3、拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。 4、move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。 **注意:** 可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached. std::thread在使用上容易出错,即std::thread对象在线程函数运行期间必须是有效的。什么意思呢? ``` #include #include void threadproc() { while(true) { std::cout << "I am New Thread!" << std::endl; } } void func() { std::thread t(threadproc); } int main() { func(); while(true) {} //让主线程不要退出 return 0; } ``` 以上代码再main函数中调用了func函数,在func函数中创建了一个线程,乍一看好像没有什么问题,但在实际运行是会崩溃。 **崩溃的原因:** **在func函数调用结束后,func中局部变量t(线程对象)被销毁,而此时线程函数仍在运行**。所以在使用std::thread类时,必须保证线程函数运行期间其线程对象有效。 std::thread对象提供了一个detach方法,通过这个方法可以让线程对象与线程函数脱离关系,这样即使线程对象被销毁,也不影响线程函数的运行。 只需要在func函数中调用detach方法即可,代码如下: ``` // 其他代码保持不变 void func() { std::thread t(threadproc); t.detach(); } ``` ### 2. lock_guard lock_guard是一个互斥量包装程序,它提供了一种方便的RAII(Resource acquisition is initialization )风格的机制来在作用域块的持续时间内拥有一个互斥量。 创建lockguard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lockguard对象的作用域时,lock_guard析构并释放互斥量。 **它的特点如下:** 1. 创建即加锁,作用域结束自动析构并解锁,无需手工解锁 2. 不能中途解锁,必须等作用域结束才解锁 3. 不能复制 ### 3. unique_lock unique_lock是一个通用的互斥量锁定包装器,它允许延迟锁定,限时深度锁定,递归锁定,锁定所有权的转移以及与条件变量一起使用。 简单地讲,uniquelock 是 lockguard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。 **特点如下:** 1. 创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定 2. 可以随时加锁解锁 3. 作用域规则同 lock_grard,析构时自动释放锁 4. 不可复制,可移动 5. 条件变量需要该类型的锁作为参数(此时必须使用unique_lock) ## 如何在C++中创建和管理线程?(考点:创建线程)【简单】 #### 如何在C++中创建和管理线程? C++11 引入了 `` 头文件,提供了标准化的线程操作接口,使得多线程编程更加便捷和可移植。 1. **创建线程** 使用 `std::thread` 创建一个线程,可以传递函数、函数指针、lambda 表达式等作为线程的执行内容。 2. 线程的生命周期管理 - **`join()`**: 主线程调用 `join()` 来等待新线程的结束。`join()` 确保主线程等待子线程完成,避免主线程提前结束,导致子线程被销毁。 - **`detach()`**: 如果不需要等待线程结束,而是希望让线程独立运行,可以调用 `detach()`。被 `detach` 的线程会在后台继续执行,主线程结束后,`std::thread` 会自动清理线程资源。 3. 线程的返回值 ``` std::thread ``` 本身不支持线程返回值。如果需要线程返回结果,可以使用 ``` std::future ``` 和 ``` std::promise ``` 来异步获取结果。 示例:使用 `std::async` 获取线程返回值 ``` std::async ``` 可以方便地创建一个线程并返回一个 ``` std::future ``` ,通过 ``` future ``` 获取线程的结果。 ``` #include #include int add(int a, int b) { return a + b; } int main() { // 使用 std::async 启动线程并返回 std::future std::future result = std::async(std::launch::async, add, 5, 3); // 获取线程结果 std::cout << "Result: " << result.get() << std::endl; return 0; } ``` 在这个例子中, ``` std::async ``` 创建了一个新的线程并执行 ``` add ``` 函数,返回一个 ``` std::future ``` 对象。调用 ``` result.get() ``` 会阻塞主线程直到获取到线程的返回值。 > 在C++中,我们通过`std::thread`类来创建线程。创建时需向其构造函数传递可调用对象——可以是普通函数、Lambda表达式或类成员函数。对于成员函数,需要同时传递对象指针和函数参数。线程启动后,必须通过`join()`或`detach()`进行生命周期管理:`join()`会确保主线程等待子线程完成,避免主线程提前结束,导致子线程被销毁;`detach()`则让线程在后台独立运行,主线程结束后,`std::thread` 会自动清理线程资源。这里有个关键点:如果未调用`join()`或`detach()`,`std::thread`析构时会强制终止程序,这是C++防止线程泄漏的安全机制。 > 在现代C++20中,更推荐使用`std::jthread`,它在析构时自动调用`join()`,彻底避免手动管理的风险。多线程环境下,共享数据必须通过同步机制保护——最常用的是`std::mutex`配合`std::lock_guard`实现RAII式加锁,或者使用`std::atomic`进行无锁编程。对于简单异步任务,直接用`std::async`封装比手动管理线程更安全高效,它能自动处理结果返回和异常传递。 实际开发中,我会通过`std::thread::hardware_concurrency()`获取硬件支持的并发数来优化线程数量,复杂场景则采用线程池模式避免频繁创建销毁。总的来说,C++线程管理的核心在于严格遵循"创建-同步-回收"的生命周期,优先选用现代工具如`jthread`和`async`,并始终确保共享数据的线程安全。 **补充点(若面试官追问)**: - **为什么`detach()`后无法控制线程?** 分离的线程由C++运行时托管,无法再获取其状态或结果,也不保证生命周期。 - **何时该用`join()` vs `detach()`?** `join()`用于需要等待结果的场景;`detach()`适用于后台监控、日志写入等无需交互的任务。 - **如何避免死锁?** 按固定顺序上锁,或用`std::lock(mtx1,mtx2)`同时锁定多个互斥量,并配合`lock_guard`的`adopt_lock`标签。 ## 请解释C++11中的thread、mutex和lock_guard。 (考点:多线程编程)【中等】 ### 简要回答 在C++11中: - `std::thread`:用于创建和管理线程。 - `std::mutex`:互斥量,用于保护共享数据,防止多个线程同时访问。 - `std::lock_guard`:是一个作用域锁,用于简化`mutex`的锁定和解锁操作。 ### 详细回答 **std::thread**: - `std::thread`类表示单个执行线程。它可以被用来包装函数或者可调用对象,从而在单独的线程中执行。 - 创建线程时,可以传递函数和参数,线程启动后会执行该函数。 - 线程可以通过`join()`方法等待其完成,或者通过`detach()`方法使其独立运行。 示例代码: cpp #include #include void func() { std::cout << "Hello from thread!" << std::endl; } int main() { std::thread t(func); t.join(); return 0; } **std::mutex**: - `std::mutex`是互斥量,提供基本的线程同步机制,用于保护共享数据。 - 当一个线程锁定了一个互斥量,其他尝试锁定同一个互斥量的线程将会阻塞,直到互斥量被解锁。 - `std::mutex`提供了`lock()`、`unlock()`和`try_lock()`方法来控制互斥量的状态。 示例代码: cpp #include #include #include std::mutex mtx; // 创建互斥量 void print_block(int n, char c) { mtx.lock(); // 锁定互斥量 for (int i = 0; i < n; ++i) { std::cout << c; } std::cout << '\n'; mtx.unlock(); // 解锁互斥量 } int main() { std::thread t1(print_block, 50, '*'); std::thread t2(print_block, 50, '$'); t1.join(); t2.join(); return 0; } **std::lock_guard**: - `std::lock_guard`是一个作用域锁,它在构造时自动锁定互斥量,并在析构时自动解锁互斥量。 - 它简化了互斥量的管理,避免了忘记解锁互斥量导致的死锁问题。 示例代码: cpp #include #include #include std::mutex mtx; // 创建互斥量 void print_block(int n, char c) { std::lock_guard[std::mutex](https://notes.kamacoder.com/bagu/2KDIUWwN9d2) guard(mtx); // 自动锁定和解锁 for (int i = 0; i < n; ++i) { std::cout << c; } std::cout << '\n'; } int main() { std::thread t1(print_block, 50, '*'); std::thread t2(print_block, 50, '$'); t1.join(); t2.join(); return 0; } ### 知识拓展 - **死锁**:当两个或多个线程永久地等待对方持有的资源时,就会发生死锁。使用`std::lock_guard`而不是手动锁定和解锁互斥量可以减少死锁的风险。 - **RAII**:`std::lock_guard`遵循RAII(Resource Acquisition Is Initialization)原则,即在对象构造时获取资源,在对象析构时释放资源,这是C++管理资源的一种常见方式。 - **其他同步机制**:除了`std::mutex`和`std::lock_guard`,C++11还提供了其他同步机制,如`std::unique_lock`(提供更多灵活性),`std::condition_variable`(用于线程间的条件同步)等。 - **原子操作**:在多线程编程中,有时可以使用原子操作来避免使用互斥量,从而提高性能。 - **锁策略**:`std::lock_guard`是独占锁,`std::shared_mutex`提供了共享锁,允许多个线程读取共享数据,但只允许一个线程写入。 - **线程局部存储**:`thread_local`关键字可以声明线程局部存储的变量,这些变量在每个线程中都有独立的实例,互不干扰。 ## C++中lock_guard和unique_lock的区别?(考点:锁管理机制)【中等】 lock_guard是自动lock和unlock,unique_lock支持手动,并且有多种方式,比如条件变量。 lock_guard是基于互斥锁std::mutex实现的,unique_lock是基于std::unique_lock实现的。 lock_guard不可以移动,unique_lock可以移动。 ## 如何理解C++中的atomic?(考点:原子操作)【中等】 原子变量,可以保证变量的赋值,修改是原子性的,要不全部完成,要么完全不操作 ## 聊一聊C++中的多线程同步机制。(考点:多线程同步)【中等】 #### 简要回答 C++中的多线程同步机制用于协调多个线程对共享资源的访问,避免数据竞争和不一致。常见的同步机制包括互斥锁(`std::mutex`)、条件变量(`std::condition_variable`)、原子操作(`std::atomic`)和信号量(`std::counting_semaphore`)。互斥锁用于保护临界区,条件变量用于线程间通信,原子操作确保操作的不可分割性,信号量用于控制资源访问数量。 #### 详细回答 1. **互斥锁(`std::mutex`)**: - 互斥锁是最基本的同步机制,用于保护临界区,确保同一时间只有一个线程可以访问共享资源。 - 使用`lock()`和`unlock()`方法手动控制锁的获取和释放,也可以使用`std::lock_guard`或`std::unique_lock`进行自动管理。 - 示例: ``` std::mutex mtx; void critical_section() { std::lock_guard lock(mtx); // 访问共享资源 } ``` 2. **条件变量(`std::condition_variable`)**: - 条件变量用于线程间的通信,允许线程等待某个条件成立后再继续执行。 - 通常与互斥锁一起使用,通过`wait()`、`notify_one()`和`notify_all()`方法实现线程的等待和唤醒。 - 示例: ``` std::mutex mtx; std::condition_variable cv; bool ready = false; void wait_for_ready() { std::unique_lock lock(mtx); cv.wait(lock, []{ return ready; }); // 条件满足后继续执行 } void set_ready() { std::lock_guard lock(mtx); ready = true; cv.notify_all(); } ``` 3. **原子操作(`std::atomic`)**: - 原子操作确保对共享变量的操作是不可分割的,避免了数据竞争。 - 适用于简单的计数器或标志位等场景。 - 示例: ``` std::atomic counter(0); void increment() { counter.fetch_add(1); } ``` 4. **信号量(`std::counting_semaphore`)**: - 信号量用于控制对共享资源的访问数量,允许多个线程同时访问有限数量的资源。 - 通过`acquire()`和`release()`方法控制资源的获取和释放。 - 示例: ``` std::counting_semaphore<10> semaphore(10); void access_resource() { semaphore.acquire(); // 访问资源 semaphore.release(); } ``` ### 知识拓展 - **死锁**:当多个线程互相等待对方释放锁时,可能导致死锁。避免死锁的策略包括按固定顺序获取锁、使用`std::lock`一次性获取多个锁、以及设置超时机制。 - **读写锁(`std::shared_mutex`)**:允许多个读线程同时访问共享资源,但写线程独占访问。适用于读多写少的场景。 - **无锁编程**:通过原子操作和内存屏障实现无锁数据结构,如无锁队列,适用于高性能场景,但实现复杂且容易出错。 - **内存模型**:C++11引入了内存模型,定义了多线程环境下的内存操作顺序,帮助开发者理解并控制多线程程序的执行顺序。 ## C++如何实现线程池?给出大体的思路?(考点:线程池的实现)【困难】 一个任务队列 一组工作线程 从任务队列中取任务 同步机制