【C++多线程编程秘籍】:std::condition_variable全面精通指南
发布时间: 2024-10-20 13:14:02 阅读量: 30 订阅数: 26
C++多线程编程实践指南:从基础到高级应用
![【C++多线程编程秘籍】:std::condition_variable全面精通指南](https://duyanshu.github.io/assets/img/posts/spurious_wakeup.png)
# 1. C++多线程编程入门
多线程编程是现代软件开发中不可或缺的一部分,它能够极大地提高应用程序的执行效率和响应速度。C++语言在C++11标准之后引入了完整的多线程库,为开发者提供了强大的多线程支持。本章节将从最基础的概念出发,逐步引导读者了解C++中的多线程编程模型,并对关键的类和函数进行初步的介绍。
## 1.1 线程的基本概念
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。在多核或多CPU的环境下,多线程可以同时执行,进而提升程序的并发性能。
## 1.2 C++中的多线程支持
C++11标准中的 `<thread>` 库提供了创建和管理线程的类和函数。开发者可以通过 `std::thread` 类创建新的线程,并使用 `join()` 和 `detach()` 等成员函数对线程进行管理。例如:
```cpp
#include <thread>
void do_something() {
// 线程要执行的代码
}
int main() {
std::thread worker(do_something); // 创建线程
worker.join(); // 等待线程完成
return 0;
}
```
## 1.3 线程同步问题
随着多线程编程的深入,我们会遇到资源竞争和线程同步问题。资源竞争通常导致数据不一致,而线程同步则确保线程之间按预期顺序和时间执行。在接下来的章节中,我们将详细探讨C++提供的多种同步机制,以解决这些问题。
# 2. std::thread的使用和管理
## 2.1 创建和启动线程
在C++11中,`std::thread`是处理多线程的基础类,它提供了一系列与线程创建和管理相关的接口。创建一个新线程相对简单,只需将一个可调用对象和其参数传递给`std::thread`的构造函数。启动线程意味着操作系统会分配资源并最终执行传入的函数。
```cpp
#include <thread>
#include <iostream>
void threadFunction() {
std::cout << "线程执行中..." << std::endl;
}
int main() {
std::thread t(threadFunction); // 创建线程
t.join(); // 等待线程结束
return 0;
}
```
上述代码展示了如何创建和启动一个线程。`threadFunction`是被调用的函数,`std::thread t(threadFunction);`创建了一个新的线程`t`来执行`threadFunction`。`t.join();`确保了主线程会等待`t`线程执行完毕后再继续执行。
创建线程时需注意,如果将一个函数对象作为参数传递,要确保该函数对象是可拷贝的。这是因为`std::thread`可能会复制传递给它的函数对象。对于不可拷贝的对象,应该使用引用传递。
## 2.2 线程的传递与转移
C++11提供了`std::thread`对象的转移语义,允许线程的所有权从一个变量转移到另一个。这使得线程的管理更加灵活,尤其是在异常安全代码中非常有用。
```cpp
void anotherThreadFunction(std::thread& t) {
// t是一个引用参数,拥有对线程的所有权
t.join();
}
int main() {
std::thread t(threadFunction);
anotherThreadFunction(t); // 将线程所有权转移给anotherThreadFunction
// t现在是不可结合的,不能再join或detach
return 0;
}
```
在上例中,`anotherThreadFunction`接受一个线程引用`t`,并调用`join`确保线程执行完成。当`t`传递给这个函数时,所有权转移给了`anotherThreadFunction`,从而使得在`main`函数中的`t`不再与任何活跃的线程相关联。
## 2.3 等待线程完成
在多线程程序中,通常需要等待一个或多个线程执行完毕。`std::thread`类提供了`join`和`detach`方法来处理线程的结束。
- `join`: 等待线程完成。这确保了程序会在所有线程完成其任务之后继续执行。当调用`join`之后,`std::thread`对象将不再与任何线程相关联。
- `detach`: 允许线程独立运行。调用`detach`之后,主线程将不会等待被分离的线程结束,且该`std::thread`对象也不再拥有线程所有权。
```cpp
int main() {
std::thread t(threadFunction);
// 主线程继续执行其他任务,不等待t线程
t.detach();
// ... 这里可以执行其他任务
return 0;
}
```
在实际应用中,应谨慎选择`join`和`detach`。如果主线程不需要等待某个线程完成,那么可以选择`detach`;否则,应当调用`join`以确保程序的线程安全和资源正确释放。
## 2.4 线程异常处理
在多线程编程中,线程函数可能会抛出异常。对于这种情况,C++标准库提供了`std::terminate`来处理未处理的异常。如果线程函数抛出异常且在线程中未被捕获,`std::terminate`将被调用,这可能导致程序异常终止。为了避免这种情况,可以在线程函数中添加异常处理机制。
```cpp
void threadFunctionWithExceptionHandling() {
try {
// 可能抛出异常的代码
} catch (...) {
// 处理所有异常
}
}
```
为了避免未处理异常导致程序终止,最好在所有线程函数中添加`try-catch`块,特别是当线程函数可能会抛出异常时。
## 2.5 线程的并行策略
在多线程程序中,使用并行策略可以提高执行效率。`std::thread`提供了基本的线程创建和管理功能,但对于更高级的线程管理,如线程池、任务分割等,可能需要额外的实现。
```cpp
// 示例代码,展示如何使用std::vector创建多个线程
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(threadFunction);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
```
在该示例中,我们创建了一个`std::vector`的线程集合,并通过循环启动多个线程执行`threadFunction`。使用`std::vector`来管理线程集合是一种常见的做法,它允许我们方便地遍历和操作线程对象。
## 2.6 线程局部存储(TLS)
线程局部存储(TLS)是一种为每个线程提供独立存储空间的技术。在C++中,可以使用`thread_local`关键字声明线程局部存储变量。TLS特别适合那些需要在线程中保持私有状态但又不希望与其他线程共享的场景。
```cpp
#include <iostream>
#include <thread>
thread_local int tlsVar = 0; // 线程局部存储变量
void threadFunction() {
tlsVar = 10; // 为当前线程的tlsVar赋值
std::cout << "线程 " << std::this_thread::get_id() << ": tlsVar = " << tlsVar << std::endl;
}
int main() {
std::thread t1(threadFunction);
std::thread t2(threadFunction);
t1.join();
t2.join();
return 0;
}
```
`thread_local`变量的生命周期和线程的生命周期相同,这意味着它会在线程结束时被销毁。 TLS在并发编程中非常有用,因为它可以为每个线程提供独立的实例,而不必担心线程间的同步问题。
# 3. :mutex
## std::mutex基础
### mutex的创建与使用
在C++中,`std::mutex`是一个用于提供互斥访问的同步原语。它保证了在任何时刻,只有一个线程可以访问某个特定的共享资源。这是通过锁定和解锁机制实现的,线程通过锁定一个互斥量来获得对共享资源的独占访问权,使用完毕后解锁,允许其他线程进行访问。
以下是创建和使用`std::mutex`的一个基本示例:
```cpp
#include <mutex>
#include <iostream>
std::mutex mtx;
void print_thread_safe(int id) {
mtx.lock(); // 锁定互斥量
std::cout << "Thread " << id << " is accessing the resource.\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟资源使用
std::cout << "Thread " << id << " is done.\n";
mtx.unlock(); // 解锁互斥量
}
int main() {
std::thread t1(print_thread_safe, 1);
std::thread t2(print_thread_safe, 2);
t1.join();
t2.join();
return 0;
}
```
在上面的代码中,`std::mutex`对象`mtx`被创建用于保护共享资源。在`print_thread_safe`函数中,通过调用`lock()`方法来锁定互斥量,然后进行线程安全的输出操作。当输出完成后,调用`unlock()`方法释放互斥量。
### mutex的类型和特性
`std::mutex`有两种类型:普通`std::mutex`和`std::timed_mutex`。
- `std::mutex`提供了最基本的锁定和解锁功能,它没有尝试锁定和带超时时间的锁定功能。
- `std::timed_mutex`提供除了`std::mutex`的功能外,还提供了尝试锁定和带超时时间的锁定功能。尝试锁定是尝试获得锁,如果不能立即获得,则不等待并返回一个状态码。带超时时间的锁定允许等待一段时间,如果在这段时间内不能获得锁,则返回一个状态码。
使用这些不同类型的`mutex`可以帮助程序员更精确地控制线程同步的需求,从而提高多线程程序的效率。
## std::lock_guard与std::unique_lock
### 详解std::lock_guard用法
`std::lock_guard`是一个基于作用域的互斥量锁,它会在构造函数中自动锁定互斥量,并在析构函数中自动解锁。这种特性确保了即使在异常发生时,也能保证互斥量被正确释放。`std::lock_guard`的使用简单且安全,适用于不需要提前解锁的情况。
以下是一个`std::lock_guard`的使用示例:
```cpp
#include <mutex>
#include <iostream>
std::mutex mtx;
void safe_print(int id) {
std::lock_guard<std::mutex> guard(mtx);
std::cout << "Thread " << id << " is accessing the resource.\n";
// 无需手动解锁,guard离开作用域时会自动解锁
}
int main() {
std::thread t1(safe_print, 1);
std::thread t2(safe_print, 2);
t1.join();
t2.join();
return 0;
}
```
在这个例子中,`std::lock_guard`对象`guard`在创建时自动调用`mtx.lock()`,在函数结束时通过析构自动调用`mtx.unlock()`。
### 探究std::unique_lock的灵活性
与`std::lock_guard`不同,`std::unique_lock`提供更灵活的锁定机制。它是一个可以延迟锁定、尝试锁定、带超时锁定的可移动但不可复制的互斥量封装器。`std::unique_lock`不仅提供了基本的锁定和解锁功能,还支持带时间限制的锁定和条件变量。
以下是一个使用`std::unique_lock`的示例:
```cpp
#include <mutex>
#include <iostream>
#include <chrono>
#include <thread>
std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx);
void safe_print(int id) {
// 尝试在500ms内获得锁
if(lock.try_lock_for(std::chrono::milliseconds(500))) {
std::cout << "Thread " << id << " is accessing the resource.\n";
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "Thread " << id << " is done.\n";
lock.unlock(); // 显式解锁
} else {
std::cout << "Thread " << id << " could not lock resource in time.\n";
}
}
int main() {
std::thread t1(safe_print, 1);
std::thread t2(safe_print, 2);
t1.join();
t2.join();
return 0;
}
```
在这个例子中,`safe_print`函数尝试在500毫秒内获得互斥量的锁,如果在规定时间内获得锁,则继续执行并手动解锁。如果未能获得锁,则输出相应的信息。
## 高级互斥锁技术
### 死锁预防和检测
死锁是多线程编程中常见的问题,当两个或多个线程相互等待对方释放锁时,就会发生死锁。死锁预防需要程序员在设计和实现代码时,注意资源的分配和锁的获取顺序。
一个简单的死锁预防策略是确保所有线程以相同的顺序请求所有资源锁。例如,如果两个资源`A`和`B`需要被锁,确保所有线程都先尝试对`A`加锁,然后再对`B`加锁。
```cpp
std::mutex mtxA, mtxB;
void lock_ordering() {
std::lock(mtxA, mtxB); // 同时锁定两个互斥量,可以防止死锁
// ... 操作资源 ...
mtxA.unlock();
mtxB.unlock();
}
```
使用`std::lock`可以同时锁定两个互斥量,这种方法可以防止在加锁过程中产生死锁。
死锁的检测则通常需要使用特定的工具或算法,例如使用资源分配图来识别循环等待条件,这些通常不是通过互斥锁直接提供的功能,而是需要程序员自己实现或使用第三方库。
### 递归锁与条件锁的应用
`std::recursive_mutex`是`std::mutex`的扩展,它允许同一个线程多次对同一个互斥量加锁。这在某些递归算法中非常有用,比如在树结构的遍历中。它同样要求解锁的次数与加锁的次数相同。
```cpp
#include <mutex>
std::recursive_mutex mtx;
void recursive_function(int depth) {
if (depth > 0) {
mtx.lock(); // 第一次调用时锁定
recursive_function(depth - 1); // 递归调用
mtx.unlock();
}
}
int main() {
recursive_function(5);
return 0;
}
```
在上面的代码中,`recursive_function`函数通过递归调用自身,每次递归调用都会增加锁定深度。
条件锁是`std::unique_lock`配合条件变量使用的一种模式。条件变量允许线程在某些条件不满足时挂起,直到其他线程修改了条件并通知条件变量。条件锁的使用场景通常与生产者-消费者模式相关。
```cpp
#include <mutex>
#include <condition_variable>
#include <thread>
#include <queue>
#include <chrono>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;
bool ready = false;
void producer() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::unique_lock<std::mutex> lock(mtx);
q.push(i);
std::cout << "Produced: " << i << std::endl;
ready = true;
cv.notify_one(); // 通知消费者线程
}
}
void consumer() {
for (int i = 0; i < 5; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 等待条件为真
int val = q.front();
q.pop();
std::cout << "Consumed: " << val << std::endl;
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
```
在这个例子中,生产者线程和消费者线程通过条件变量`cv`和互斥锁`mtx`配合使用,实现生产者-消费者模式。消费者线程会在`ready`标志为`true`时被条件变量`cv`唤醒并处理队列中的数据。
# 4. std::condition_variable的理论与实践
在多线程编程中,线程间的同步是非常关键的议题。std::condition_variable是一个同步机制,允许线程在某个条件成立之前等待,同时允许其他线程在条件满足后唤醒等待的线程。本章将会深入探讨条件变量的工作原理和基本用法,同时介绍如何在实际项目中应用std::condition_variable解决复杂的线程同步问题。
## 4.1 条件变量的工作原理
### 4.1.1 什么是条件变量
条件变量是一种线程同步原语,主要用于线程间的协调,特别是在生产者-消费者模式中非常有用。它能够阻塞一个或多个线程直到某个条件被某个线程改变。std::condition_variable类在<condition_variable>头文件中声明。
```cpp
#include <condition_variable>
#include <mutex>
std::mutex mtx;
std::condition_variable cv;
void do_wait() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock); // 线程将在此处阻塞等待
// 当前线程被唤醒后会继续执行这里的代码
}
```
条件变量需要与互斥锁一起使用,以保证等待和通知的正确同步。
### 4.1.2 条件变量与互斥锁的协同工作
为了防止竞争条件,std::condition_variable要求在等待操作期间持续保持锁的锁定状态。通常,这通过使用一个与互斥锁相关联的std::unique_lock或std::lock_guard对象来实现。当线程调用wait时,该锁将被自动释放,并在等待结束时重新获取。
```cpp
void wait_example() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return /* 条件 */; }); // 当条件不满足时,线程进入等待状态
}
```
在上述代码中,lock保证了条件变量在临界区的安全使用。如果条件尚未满足,wait会将线程置于等待状态并释放锁,使其他线程可以修改条件并调用notify_one或notify_all来唤醒等待的线程。
## 4.2 条件变量的基本用法
### 4.2.1 等待条件成立的标准模式
使用std::condition_variable时,一个典型的等待模式如下所示:
```cpp
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return /* 条件 */; }); // 等待条件成立
// 条件成立后继续执行的代码
```
这里使用了一个lambda表达式来检查条件。wait函数的行为依赖于这个lambda表达式的返回值。如果返回true,线程将退出等待状态并继续执行。如果返回false,线程继续等待。
### 4.2.2 通知机制:唤醒等待线程
条件变量提供了两种通知机制:`notify_one`和`notify_all`。`notify_one`会唤醒一个等待的线程,而`notify_all`会唤醒所有等待的线程。
```cpp
cv.notify_one(); // 唤醒一个等待中的线程
cv.notify_all(); // 唤醒所有等待中的线程
```
使用`notify_one`时,通常不关心哪个线程被唤醒,因为它可以随机唤醒一个线程。而`notify_all`适用于那些需要确保所有线程都被通知到的情况。选择哪一种方法取决于具体的应用场景。
## 4.3 高级条件变量应用
### 4.3.1 误唤醒与虚假唤醒问题
当使用条件变量时,线程可能会遇到误唤醒(spurious wakeups)问题。即使条件未满足,某些因素也可能会导致线程被唤醒。这意味着在wait之后,我们通常需要再次检查条件是否确实满足。
```cpp
while (!/* 条件 */) {
cv.wait(lock);
}
```
使用循环而不是条件表达式是处理虚假唤醒的推荐方式。
### 4.3.2 条件变量与时间限制的结合
条件变量允许我们结合时间限制等待条件,可以使用`std::cv_status`来处理超时:
```cpp
std::cv_status status = cv.wait_for(lock, std::chrono::seconds(1));
if (status == std::cv_status::timeout) {
// 超时处理
}
```
这段代码表明线程将会等待最多1秒。如果在时间结束之前条件得到满足,wait_for将返回std::cv_status::no_timeout,否则返回std::cv_status::timeout。这是一种非常有用的技术,尤其是在需要避免无限等待的场景中。
条件变量提供了一种强大的机制,使得线程间可以在适当的时候相互通知,而不会造成CPU资源的浪费。通过上述的章节介绍,我们可以看到如何在实际编程中有效地利用条件变量来管理线程间的协作。
# 5. C++多线程编程实战案例
## 5.1 线程安全的队列实现
### 5.1.1 设计思想和类的结构
在多线程环境下实现一个线程安全的队列是并发编程中的一个经典问题。设计这种队列的关键在于如何确保在并发访问时数据的一致性和线程安全。我们可以设计一个类,使用互斥锁(mutex)来保护数据,并利用条件变量(condition_variable)来实现线程间的通知机制。
```cpp
#include <mutex>
#include <condition_variable>
#include <queue>
#include <memory>
template<typename T>
class ThreadSafeQueue {
public:
ThreadSafeQueue() {}
void Enqueue(const T& value) {
std::unique_lock<std::mutex> lock(mutex_);
queue_.push(value);
condition_variable_.notify_one();
}
bool Dequeue(T& value) {
std::unique_lock<std::mutex> lock(mutex_);
condition_variable_.wait(lock, [this] { return !queue_.empty(); });
value = queue_.front();
queue_.pop();
return true;
}
private:
std::mutex mutex_;
std::queue<T> queue_;
std::condition_variable condition_variable_;
};
```
在这个简单的模板类中,我们使用了模板以便队列可以存储任意类型的数据。类的成员变量包括一个互斥锁(用于线程间同步),一个队列(存储数据项)和一个条件变量(用于线程间通信)。`Enqueue`函数用于添加元素到队列中,而`Dequeue`函数则用于从队列中移除元素。
### 5.1.2 实现生产者-消费者模型
生产者-消费者模型是多线程编程中常见的模式,该模式中生产者线程负责生成数据项并放入队列中,而消费者线程则负责从队列中取出数据项并进行处理。
```cpp
void Producer(ThreadSafeQueue<int>& queue, int count) {
for (int i = 0; i < count; ++i) {
queue.Enqueue(i);
std::cout << "Produced: " << i << std::endl;
}
}
void Consumer(ThreadSafeQueue<int>& queue) {
int value;
while (queue.Dequeue(value)) {
std::cout << "Consumed: " << value << std::endl;
}
}
int main() {
ThreadSafeQueue<int> queue;
std::thread producer(Producer, std::ref(queue), 10);
std::thread consumer1(Consumer, std::ref(queue));
std::thread consumer2(Consumer, std::ref(queue));
producer.join();
consumer1.join();
consumer2.join();
return 0;
}
```
在上述代码中,我们创建了一个生产者线程和两个消费者线程。生产者线程向队列中添加元素,而消费者线程从队列中取出并消费元素。注意,所有对队列的操作都是通过线程安全的函数实现的,保证了多线程访问的正确性。
## 5.2 线程池的设计与应用
### 5.2.1 线程池的架构和工作原理
线程池(Thread Pool)是一种多线程处理形式,它避免了线程创建和销毁的开销,可复用线程并有效控制线程数。线程池通常由一个任务队列、一组工作线程和一些同步机制组成。
工作线程从任务队列中获取任务,执行完成后再次从队列中获取新的任务,直到线程池被关闭。线程池可以限制同时运行的最大线程数量,并且可以排队等待任务。
### 5.2.2 使用std::condition_variable优化线程池
通过条件变量,线程池中的工作线程可以等待任务到来,而不是忙等。这样不仅提高了CPU的使用效率,而且减少了线程上下文切换的开销。
```cpp
#include <thread>
#include <vector>
#include <atomic>
#include <functional>
#include <future>
class ThreadPool {
public:
ThreadPool(size_t threads) : stop(false) {
for(size_t i = 0; i < threads; ++i)
workers.emplace_back([this] {
while(true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock, [this] {
return this->stop || !this->tasks.empty();
});
if(this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
});
}
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
if(stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers)
worker.join();
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
std::atomic<bool> stop;
};
```
这里,`ThreadPool`类使用`std::function`来存储可调用任务,并通过`std::packaged_task`来存储任务的返回值。工作线程会等待队列中有任务到来,然后执行。这样的设计充分利用了条件变量的等待/通知机制,减少了空循环对CPU的浪费。
## 5.3 并发算法优化
### 5.3.1 并行排序算法的实现
并行排序算法在处理大量数据时可以显著提高效率。在C++中,我们可以使用多线程技术来实现高效的并行排序算法,例如快速排序和归并排序。
这里,我们实现一个简单的并行快速排序算法:
```cpp
void ParallelQuickSort(std::vector<int>& vec, int left, int right) {
if (left >= right) {
return;
}
int pivot = vec[(left + right) / 2];
int i = left, j = right;
while (i <= j) {
while (vec[i] < pivot) i++;
while (vec[j] > pivot) j--;
if (i <= j) {
std::swap(vec[i], vec[j]);
i++;
j--;
}
}
// 并行处理两个子区间
std::async(std::launch::async, ParallelQuickSort, std::ref(vec), left, j);
std::async(std::launch::async, ParallelQuickSort, std::ref(vec), i, right);
}
int main() {
std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
ParallelQuickSort(vec, 0, vec.size() - 1);
for (int num : vec) {
std::cout << num << ' ';
}
std::cout << std::endl;
return 0;
}
```
### 5.3.2 利用条件变量减少线程同步开销
当多个线程需要执行一个任务的一部分,并在执行完毕后得到通知继续执行时,可以使用条件变量减少线程的空等待时间和同步开销。
例如,在并行算法的合并阶段,当两个子任务都完成时,主线程需要得到通知后才继续执行合并操作。
```cpp
void ParallelMerge(std::vector<int>& vec, int left, int mid, int right) {
std::vector<int> left_vec(vec.begin() + left, vec.begin() + mid + 1);
std::vector<int> right_vec(vec.begin() + mid + 1, vec.begin() + right + 1);
// 并行计算两个子数组的逆序
std::future<void> left_task = std::async(std::launch::async,
[&left_vec]() {
std::reverse(left_vec.begin(), left_vec.end());
});
std::future<void> right_task = std::async(std::launch::async,
[&right_vec]() {
std::reverse(right_vec.begin(), right_vec.end());
});
// 等待两个子任务完成
left_task.get();
right_task.get();
// 合并两个已逆序的子数组
merge(vec, left, mid, right, left_vec, right_vec);
}
void merge(std::vector<int>& vec, int left, int mid, int right,
std::vector<int>& left_vec, std::vector<int>& right_vec) {
int i = 0, j = 0, k = left;
while (i < left_vec.size() && j < right_vec.size()) {
if (left_vec[i] < right_vec[j])
vec[k++] = left_vec[i++];
else
vec[k++] = right_vec[j++];
}
// 将剩余的元素复制回原数组
while (i < left_vec.size())
vec[k++] = left_vec[i++];
while (j < right_vec.size())
vec[k++] = right_vec[j++];
}
```
在这个例子中,我们分别对左右两个子数组进行逆序操作,使用`std::async`和`std::future`实现异步处理,并通过`get`函数等待两个子任务的完成。这种策略能有效减少线程的空等待时间,提高程序效率。
0
0