线程同步保证了多个线程在并发执行时的正确性,而线程通信则是为了使多个线程之间能够相互传递信息。在C++中,我们可以使用不同的机制来实现线程同步和通信,主要包括互斥量(mutex)、条件变量(condition_variable)、读写锁(shared_mutex)、信号量(semaphore)等。小编将详细介绍这些常见的同步和通信机制,并讨论其使用场景。
一、线程同步的常见方法
1. 互斥量(mutex)
互斥量(mutex) 是最常用的线程同步机制,它用于保证同一时刻只有一个线程能够访问共享资源。通过互斥量,可以避免多个线程同时访问共享资源导致的数据竞争和不一致性问题。
使用方法: C++标准库提供了 std::mutex 类来实现互斥量,并通过 std::lock_guard 或 std::unique_lock 来自动管理锁的加锁与解锁。
示例代码:
cppCopy Code#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 定义互斥量
void print_number(int n) {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁
std::cout << "Number: " << n << std::endl;
}
int main() {
std::thread t1(print_number, 1);
std::thread t2(print_number, 2);
t1.join();
t2.join();
return 0;
}
解释:
std::mutex 用于保护共享资源。在这里是 std::cout。
std::lock_guard 是一个RAII类型的类,它在作用域内自动加锁并在作用域结束时自动解锁,避免了手动解锁可能引发的错误。
2. 读写锁(shared_mutex)
C++17引入了 std::shared_mutex,用于实现读写锁机制。它允许多个线程同时读取共享资源,但在写线程占用资源时,其他线程不能同时读或写。
使用方法:
std::shared_lock 用于读操作,允许多个线程并行读取。
std::unique_lock 用于写操作,保证写操作时没有其他线程访问该资源。
示例代码:
cppCopy Code#include <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex rw_mutex; // 定义读写锁
int shared_data = 0;
void reader() {
std::shared_lock<std::shared_mutex> lock(rw_mutex); // 读取时共享锁
std::cout << "Reader thread reads: " << shared_data << std::endl;
}
void writer(int value) {
std::unique_lock<std::shared_mutex> lock(rw_mutex); // 写入时独占锁
shared_data = value;
std::cout << "Writer thread writes: " << value << std::endl;
}
int main() {
std::thread t1(reader);
std::thread t2(writer, 42);
std::thread t3(reader);
t1.join();
t2.join();
t3.join();
return 0;
}
解释:
读线程通过 std::shared_lock 进行共享锁定,允许多个线程并行读取。
写线程通过 std::unique_lock 独占锁定,保证数据一致性。
3. 条件变量(condition_variable)
条件变量是用于线程间同步的机制,允许一个线程等待某个条件满足,或者通知其他线程某个条件已经发生改变。条件变量通常与互斥量一起使用。
使用方法:
std::condition_variable 用于线程等待和通知。
std::mutex 用于保护共享资源。
示例代码:
cppCopy Code#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lock<std::mutex> lock(mtx);
while (!ready) cv.wait(lock); // 等待条件变量通知
std::cout << "Thread " << id << std::endl;
}
void go() {
std::unique_lock<std::mutex> lock(mtx);
ready = true;
cv.notify_all(); // 通知所有等待的线程
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(print_id, i);
}
std::cout << "Waiting..." << std::endl;
go(); // 改变条件,通知线程开始执行
for (auto& th : threads) {
th.join();
}
return 0;
}
解释:
cv.wait(lock) 会使线程阻塞,直到条件满足。
cv.notify_all() 通知所有等待的线程继续执行。
二、线程通信的常见方法
1. 使用共享数据结构
线程间可以通过共享数据结构来进行通信。例如,一个线程可以将结果写入共享队列,另一个线程从队列中读取数据。这种方式非常适用于生产者-消费者模型。
示例代码(使用 std::queue 和 std::mutex):
cppCopy Code#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
std::queue<int> data_queue;
std::mutex mtx;
std::condition_variable cv;
void producer() {
for (int i = 0; i < 5; ++i) {
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
std::cout << "Produced: " << i << std::endl;
cv.notify_all(); // 通知消费者有数据可用
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !data_queue.empty(); }); // 等待数据
int value = data_queue.front();
data_queue.pop();
std::cout << "Consumed: " << value << std::endl;
if (value == 4) break; // 消费到一定数据后退出
}
}
int main() {
std::thread prod(producer);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}
解释:
生产者线程将数据放入 data_queue 队列中,并通知消费者线程。
消费者线程从队列中取出数据进行处理。
2. 信号量(semaphore)
信号量是一种计数同步机制,用于控制对共享资源的访问。C++20引入了 std::counting_semaphore,可以用来控制多个线程对资源的访问。
使用方法:
std::counting_semaphore 用于管理线程访问的许可数量。
示例代码:
cppCopy Code#include <iostream>
#include <thread>
#include <semaphore>
std::counting_semaphore<3> sem(3); // 最多允许3个线程同时执行
void access_resource(int id) {
sem.acquire(); // 获取许可
std::cout << "Thread " << id << " is accessing the resource." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
sem.release(); // 释放许可
}
int main() {
std::thread t1(access_resource, 1);
std::thread t2(access_resource, 2);
std::thread t3(access_resource, 3);
std::thread t4(access_resource, 4);
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}
解释:
std::counting_semaphore 控制允许同时访问共享资源的线程数量。
acquire 获取许可,release 释放许可。
C++提供了多种线程同步和通信机制来帮助开发者在多线程编程中保持数据一致性与安全性。常用的线程同步方法包括 互斥量、读写锁 和 条件变量,它们可以帮助我们解决线程之间的竞争问题。而线程通信则通常使用共享数据结构、信号量等机制,使得线程之间可以安全地传递数据。掌握这些方法并灵活应用,能够有效提高多线程程序的性能和稳定性。