互斥量和条件变量:深入解析与代码实现
在并发编程中,互斥量和条件变量是两种非常重要的同步机制,它们分别用于保护共享资源不被多个线程同时访问,以及协调线程之间的执行顺序。本文将深入探讨互斥量和条件变量的工作原理,并通过代码示例展示如何在实际开发中使用它们。
一、互斥量(Mutex)
互斥量,也被称为互斥锁或互斥对象,是一种常用的同步原语,用于保护共享资源的访问。当一个线程获得互斥量的锁时,其他尝试获取该锁的线程将被阻塞,直到锁被释放。
在C++中,std::mutex是一个标准的互斥量实现。下面是一个简单的示例,展示了如何使用std::mutex来保护一个共享计数器:
#include <iostream> #include <thread> #include <vector> #include <mutex> std::mutex mtx; // 全局互斥量 int counter = 0; // 共享计数器 void increment(int id) { mtx.lock(); // 请求互斥量的锁 ++counter; // 修改共享资源 std::cout << "Thread " << id << " incremented counter to " << counter << std::endl; mtx.unlock(); // 释放互斥量的锁 } int main() { std::vector<std::thread> threads; const int num_threads = 10; // 创建多个线程,每个线程都会调用increment函数 for (int i = 0; i < num_threads; ++i) { threads.push_back(std::thread(increment, i)); } // 等待所有线程完成 for (auto& th : threads) { th.join(); } std::cout << "Final counter value is " << counter << std::endl; return 0; }
在这个例子中,每个线程都会调用increment函数来增加共享计数器counter的值。通过使用mtx.lock()和mtx.unlock()来包裹对counter的访问,我们确保了每次只有一个线程能够修改counter的值。如果没有互斥量的保护,多个线程可能会同时修改counter,导致数据竞争和不一致的结果。
二、条件变量(Condition Variable)
条件变量用于线程间的同步,允许一个或多个线程等待某个条件成立(通常由另一个线程触发)。条件变量通常与互斥量一起使用,以确保在检查条件或修改共享数据时不会发生数据竞争。
在C++中,std::condition_variable是一个标准的条件变量实现。下面是一个使用条件变量的示例,其中有一个生产者线程生产数据,一个消费者线程消费数据:
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> std::queue<int> data_queue; // 数据队列 std::mutex mtx; // 互斥量,保护数据队列 std::condition_variable cond_var; // 条件变量 bool stop = false; // 停止标志 void producer() { for (int i = 0; i < 10; ++i) { std::unique_lock<std::mutex> lock(mtx); data_queue.push(i); lock.unlock(); cond_var.notify_one(); // 通知一个等待的线程 std::this_thread::sleep_for(std::chrono::seconds(1)); } { std::lock_guard<std::mutex> lock(mtx); stop = true; } cond_var.notify_all(); // 通知所有等待的线程 } void consumer() { while (true) { std::unique_lock<std::mutex> lock(mtx); cond_var.wait(lock, [] { return !data_queue.empty() || stop; }); // 等待数据可用或收到停止信号 if (stop && data_queue.empty()) { break; // 所有数据已消费且收到停止信号,退出循环 } int data = data_queue.front(); data_queue.pop(); lock.unlock(); std::cout << "Consumed: " << data << std::endl; } } int main() { std::thread producer_thread(producer); std::thread consumer_thread(consumer); producer_thread.join(); consumer_thread.join(); return 0; }
在这个例子中,生产者线程producer将整数推送到data_queue中,然后通知条件变量cond_var。消费者线程consumer在一个循环中等待条件变量cond_var,直到队列非空或有停止信号为止。当队列非空时,消费者线程将取出数据并处理它。当所有数据都被消费且收到停止信号时,消费者线程将退出循环。
值得注意的是,我们在使用cond_var.wait()时传入了一个std::unique_lock对象,该对象在调用wait()时会自动释放互斥量,并在条件满足后重新获取,从而避免了死锁。同时,我们传递给wait()的Lambda表达式作为条件检查函数,确保只有在队列非空或接收到停止信号时线程才会被唤醒。
总结来说,互斥量和条件变量是并发编程中非常重要的同步工具。互斥量用于保护共享资源,确保同一时间只有一个线程能够访问它;而条件变量则用于协调线程间的执行顺序,允许线程等待某个条件成立。通过使用这些同步机制,我们可以编写出安全、可靠的并发代码。