【C++多线程编程秘籍】:std::condition_variable全面精通指南

发布时间: 2024-10-20 13:14:02 阅读量: 30 订阅数: 26
PDF

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`函数等待两个子任务的完成。这种策略能有效减少线程的空等待时间,提高程序效率。
corwn 最低0.47元/天 解锁专栏
买1年送3月
点击查看下一篇
profit 百万级 高质量VIP文章无限畅学
profit 千万级 优质资源任意下载
profit C知道 免费提问 ( 生成式Al产品 )

相关推荐

SW_孙维

开发技术专家
知名科技公司工程师,开发技术领域拥有丰富的工作经验和专业知识。曾负责设计和开发多个复杂的软件系统,涉及到大规模数据处理、分布式系统和高性能计算等方面。
专栏简介
本专栏深入探讨了 C++ 中的 std::condition_variable,一种强大的同步机制,用于线程间通信和并发控制。从基本原理到高级用法,本指南涵盖了 std::condition_variable 的各个方面。 通过生产者-消费者模型,读者将了解 std::condition_variable 在并发编程中的革命性应用。深入解析其工作原理和在并发控制中的角色,有助于避免死锁和确保线程安全。高级用法和最佳实践提供了实用技巧,以充分利用 std::condition_variable。 此外,本专栏探讨了 std::condition_variable 与协同工作原理、事件驱动编程模型和原子操作的协作使用。通过对错误处理和异常安全的实战分析,读者可以掌握 std::condition_variable 的高级技巧。 本指南还涵盖了 std::condition_variable 在复杂同步场景中的应用案例,以及与原子操作的对比。通过对通知机制和等待队列管理的探究,读者将深入了解 std::condition_variable 在实时系统中的挑战。
最低0.47元/天 解锁专栏
买1年送3月
百万级 高质量VIP文章无限畅学
千万级 优质资源任意下载
C知道 免费提问 ( 生成式Al产品 )

最新推荐

【安全编程艺术】:BCprov-jdk15on-1.70实践案例教你构建安全Java应用

![【安全编程艺术】:BCprov-jdk15on-1.70实践案例教你构建安全Java应用](https://img-blog.csdnimg.cn/fff444e637da46b8be9db0e79777178d.png) # 摘要 随着信息技术的快速发展,安全编程成为保障软件安全的关键环节,特别是在Java平台上的加密技术应用。本文首先介绍了安全编程的基础知识和Java平台,随后深入探讨了BCprov-jdk15on-1.70加密库,并详细解释了在Java中实施加密技术的实践方法,包括对称与非对称加密、消息摘要以及完整性校验。第四章进一步阐述了Java安全编程的高级应用,包括安全密钥管

CH341A驱动安装指南:一站式解决兼容性挑战

![CH341A驱动安装指南:一站式解决兼容性挑战](https://reversepcb.com/wp-content/uploads/2023/04/CH341A-Programmer-USB-Bus-Convert-Module.jpg) # 摘要 CH341A是一款常用于USB转串口通信的芯片,广泛应用于各类硬件设备。本文首先概述CH341A驱动的基本信息,然后深入探讨该芯片的功能、应用领域以及常见的型号区别。接着,文章详细分析了操作系统和硬件平台兼容性所面临的挑战,并提出了驱动安装前的准备工作,包括确认系统环境和下载适合的驱动程序。文章还详细介绍了在不同操作系统(Windows、L

【MySQL快速入门】:5步教你Linux下搭建高效数据库

![【MySQL快速入门】:5步教你Linux下搭建高效数据库](https://img-blog.csdnimg.cn/direct/bdd19e49283d4ad489b732bf89f22355.png) # 摘要 本文首先对MySQL数据库和Linux环境的准备工作进行了概述,然后详细介绍了MySQL在Linux系统下的安装、配置、启动与管理过程。接着,本文深入探讨了MySQL的基础操作和数据管理技巧,包括基础命令、数据操作以及高级管理技术如索引优化和事务处理。此外,文章还提供了MySQL性能优化和安全管理的策略,并通过实际案例分析了性能调优和故障处理的解决方案。最后,本文探讨了My

敏捷开发新纪元:将DIN70121标准融入软件开发生命周期

![DIN70121标准](http://www.shfateng.com/uploads/upi/image/20230424/20230424133844_17410.png) # 摘要 本文旨在探讨敏捷开发与DIN70121标准的理论与实践应用。首先概述了敏捷开发的核心原则和方法论,以及DIN70121标准的历史、内容和要求。文章进一步分析了DIN70121标准在软件开发生命周期中的应用,并通过案例研究展示了敏捷环境下的实际应用。接着,文章构建了敏捷开发与DIN70121标准的融合模型,并讨论了实施步骤、最佳实践和持续改进策略。最后,文章展望了敏捷开发的未来趋势,分析了标准化与定制化之

【充电桩应用层协议详解】:数据交换与处理机制优化策略

![【充电桩应用层协议详解】:数据交换与处理机制优化策略](https://pub.mdpi-res.com/electronics/electronics-08-00096/article_deploy/html/images/electronics-08-00096-ag.png?1570955282) # 摘要 随着新能源汽车的普及,充电桩的高效、安全通信变得至关重要。本文首先概述了充电桩应用层协议,并分析了其数据交换机制,包括数据封装过程、传输层协议角色以及安全性措施。随后,深入探讨了数据处理机制,涉及采集、预处理、解析、转换以及相关的优化策略和智能化技术。在此基础上,提出了协议性能

【矿用本安电源电磁兼容性设计】:理论与实践应用指南

![【矿用本安电源电磁兼容性设计】:理论与实践应用指南](https://emzer.com/wp-content/uploads/2022/06/Capture-1-1024x472.png) # 摘要 矿用本安电源在复杂的电磁环境下保持电磁兼容性至关重要,以确保运行安全和可靠性。本文首先介绍了电磁兼容性的基础理论,包括其定义、重要性、标准概述、电磁干扰与敏感度的分类及评估方法。随后,本文聚焦于矿用本安电源的电磁兼容性设计实践,包括硬件设计中的EMC优化、PCB布局原则、软件滤波技术、故障安全策略以及防护与隔离技术的应用。此外,文章还探讨了电磁兼容性的测试与验证方法,通过案例分析了测试实例

【IO-LINK与边缘计算】:数据处理优化的终极之道

![【IO-LINK与边缘计算】:数据处理优化的终极之道](https://www.es.endress.com/__image/a/6005772/k/3055f7da673a78542f7a9f847814d036b5e3bcf6/ar/2-1/w/1024/t/jpg/b/ffffff/n/true/fn/IO-Link_Network_Layout2019_1024pix_EN_V2.jpg) # 摘要 本文首先对IO-LINK技术进行概述,继而深入探讨边缘计算的基础知识及其在工业物联网中的应用。文章着重分析了边缘计算的数据处理模型,并讨论了IO-LINK与边缘计算结合后的优势和实际

【触摸屏人机界面设计艺术】:汇川IT7000系列实用设计原则与技巧

# 摘要 本文全面探讨了触摸屏人机界面的设计原则、实用技巧以及性能优化。首先概述了人机界面的基本概念和设计基础,包括简洁性、直观性、一致性和可用性。接着,文章深入讨论了认知心理学在人机交互中的应用和用户体验与界面响应时间的关系。对触摸屏技术的工作原理和技术比较进行了介绍,为IT7000系列界面设计提供了理论和技术支持。本文还涉及了界面设计中色彩、图形、布局和导航的实用原则,并提出了触摸操作优化的策略。最后,通过界面设计案例分析,强调了性能优化和用户测试的重要性,讨论了代码优化、资源管理以及用户测试方法,以及根据用户反馈进行设计迭代的重要性。文章的目标是提供一套全面的设计、优化和测试流程,以改进

【电路设计中的寄生参数识别】:理论与实践的完美结合

![starrc寄生参数提取与后仿.docx](https://static.mianbaoban-assets.eet-china.com/xinyu-images/MBXY-CR-d6172a7accea9f4343f589c23b6f8b9a.png) # 摘要 寄生参数,包括电阻、电容和电感,在电路设计中扮演着关键角色,尤其是在高频和功率电路中。本文详细探讨了寄生参数的基本概念、在电路设计中的作用、模拟与仿真、测量技术以及管理与控制策略。通过深入分析寄生参数的来源、形成、影响以及优化策略,本文旨在提供一套全面的框架,帮助工程师在电路设计和制造过程中识别和管理寄生效应,提高电路的性能和

【刷机风险管理】:避免刷机失败的实用策略

![【刷机风险管理】:避免刷机失败的实用策略](https://opengraph.githubassets.com/46da4c8858280dac0909ba646ad8504f9a45717f7df717dbc9b24716c5e07971/Sinnefa/Android-Apps-and-Data-Backup-and-Restore-Linux-Bash-Script) # 摘要 刷机作为对设备进行系统升级和个性化的手段,虽然带来了便利和功能增强,但也伴随着潜在风险。本文详细概述了刷机风险管理的重要性,并从刷机前的风险评估与准备,刷机过程中的风险控制,以及刷机后的风险管理与维护三个