1、读写锁(std::shared_mutex
)(C++17)
读写锁(std::shared_mutex
)是C++17中引入的一种同步原语,用于允许多个线程同时读取共享数据,但在写入数据时只允许一个线程进行操作。这种锁可以提高多线程程序的性能,因为它允许多个线程同时读取数据,而不需要等待其他线程完成读取操作。这种锁适用于读操作远多于写操作的场景,因为在这种场景下,读写锁可以提高性能。
下面是一个使用std::shared_mutex
的简单示例:
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>// 共享数据结构
struct Data {int value;std::shared_mutex mutex;
};// 读取数据的函数
void read_data(const Data& data) {std::shared_lock<std::shared_mutex> lock(data.mutex); // 获取共享锁std::cout << "Read value: " << data.value << std::endl;
}// 写入数据的函数
void write_data(Data& data, int new_value) {std::unique_lock<std::shared_mutex> lock(data.mutex); // 获取独占锁data.value = new_value;std::cout << "Write value: " << data.value << std::endl;
}int main() {Data data{0, std::shared_mutex()};// 创建多个读取线程std::vector<std::thread> readers;for (int i = 0; i < 5; ++i) {readers.emplace_back(read_data, std::ref(data));}// 创建多个写入线程std::vector<std::thread> writers;for (int i = 0; i < 2; ++i) {writers.emplace_back(write_data, std::ref(data), i + 1);}// 等待所有线程完成for (auto& reader : readers) {reader.join();}for (auto& writer : writers) {writer.join();}return 0;
}
在这个示例中,我们定义了一个名为Data的结构体,其中包含一个整数值和一个std::shared_mutex
。我们还定义了两个函数:read_data
和write_data
,分别用于读取和写入数据。在这两个函数中,我们使用std::shared_lock
和std::unique_lock
来获取共享锁和独占锁。
在main
函数中,我们创建了多个读取线程和写入线程。这些线程将并发地读取和写入数据。由于我们使用了std::shared_mutex
,所以多个读取线程可以同时访问数据,而写入线程会阻塞其他线程的访问。
总之,std::shared_mutex
是一种高效的同步原语,可以帮助我们在多线程程序中实现对共享数据的并发访问。
互斥锁(Mutex
)和读写锁(ReadWriteLock
)是两种常用的同步机制,用于保护共享资源,防止多个线程同时访问导致数据不一致的问题。
互斥锁是一种简单的同步机制,它只关心锁的状态-锁定或未锁定。当一个线程想要访问共享资源时,必须先获得互斥锁。如果互斥锁已经被其他线程锁定,那么该线程会进入阻塞状态,直到互斥锁被解锁。这种机制确保了在任何时候,只有一个线程可以访问共享资源。
读写锁则是一种更复杂的同步机制,它区分了读操作和写操作。当一个线程想要读取共享资源时,只需要获取读锁,这允许多个线程同时读取共享资源。但是,如果有一个线程想要写入共享资源,它必须首先获得写锁,这会阻止其他所有线程(包括读操作和写操作)访问共享资源,直到写锁被解锁。
2、std::scoped_lock
(C++17)
std::scoped_lock
是C++标准库中的一个类模板,它提供了一种方便的方式来管理互斥锁的生命周期。这个类模板可以用于锁定和解锁互斥量,同时确保在作用域结束时自动释放锁,从而避免了因忘记解锁而导致的死锁问题。下面是一个使用std::scoped_lock
的示例代码:
#include <iostream>
#include <mutex>
#include <thread>std::mutex mtx; // 定义一个互斥锁void print_hello() {std::scoped_lock lock(mtx); // 使用std::scoped_lock管理互斥锁std::cout << "Hello, world!" << std::endl;
} // 当函数执行完毕时,lock对象会自动析构并释放锁int main() {std::thread t1(print_hello);std::thread t2(print_hello);t1.join();t2.join();return 0;
}
在这个示例中,我们定义了一个互斥锁mtx
,然后在print_hello
函数中使用std::scoped_lock
来管理这个互斥锁。当print_hello
函数被调用时,std::scoped_lock
对象lock
会锁定互斥锁mtx
,并在函数执行完毕后自动释放锁。这样可以确保在多线程环境下,只有一个线程能够访问共享资源,从而避免了竞态条件。
3、线程安全队列的实现
#include <queue>
#include <mutex>
#include <condition_variable>template <typename T>
class ThreadSafeQueue {
private:std::queue<T> queue_;std::mutex mutex_;std::condition_variable cond_;public:ThreadSafeQueue() = default;ThreadSafeQueue(const ThreadSafeQueue<T>& other) {std::lock_guard<std::mutex> lock(other.mutex_);queue_ = other.queue_;}void push(T value) {std::lock_guard<std::mutex> lock(mutex_);queue_.push(value);cond_.notify_one();}T pop() {std::unique_lock<std::mutex> lock(mutex_);while (queue_.empty()) {cond_.wait(lock);}T value = queue_.front();queue_.pop();return value;}
};
std::queue<T> queue_
:这是一个标准库中的队列,用于存储数据。
std::mutex mutex_
:这是一个互斥锁,用于保护队列的访问,防止多个线程同时修改队列。
std::condition_variable cond_
:这是一个条件变量,用于阻塞和唤醒等待队列的线程。
push(T value)
:这个方法用于向队列中添加元素。首先,它会锁定互斥锁,然后将元素添加到队列中,最后通过条件变量通知一个正在等待的线程。
pop()
:这个方法用于从队列中取出元素。首先,它会锁定互斥锁,然后检查队列是否为空。如果队列为空,那么它会通过条件变量等待直到队列不为空。当队列不为空时,它会取出队列的第一个元素,然后返回这个元素。