读写锁
在C++中,读写锁(也称为共享-独占锁)是一种同步机制,用于允许多个线程同时读取某个资源,但在写入资源时需要独占访问。这种锁对于提高多线程程序中的读取操作的并发性非常有用,尤其是在读取操作远多于写入操作的场景下。
C++17标准引入了std::shared_mutex,这是一个读写锁的实现。std::shared_mutex提供了共享锁定(shared locking)和独占锁定(exclusive locking)两种模式。多个线程可以同时获得共享锁,但只有一个线程可以获得独占锁。
以下是一个使用std::shared_mutex的简单示例:
#include <iostream>
#include <shared_mutex>
#include <string>
#include <thread>
#include <vector>
std::shared_mutex rwMutex;
std::string data = "Initial data";
void reader() {
std::shared_lock<std::shared_mutex> lock(rwMutex);
std::cout << "Read data: " << data << std::endl;
}
void writer(const std::string& newData) {
std::unique_lock<std::shared_mutex> lock(rwMutex);
data = newData;
std::cout << "Write data: " << data << std::endl;
}
int main() {
std::vector<std::thread> threads;
// 创建读者线程
for (int i = 0; i < 5; ++i) {
threads.emplace_back(reader);
}
// 创建写者线程
threads.emplace_back(writer, "Updated data");
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
return 0;
}
自旋锁
atomic_bool 实现自旋锁
atomic_bool实现自旋锁就是完全依靠原子性的CAS操作了,具体来说,由atomic_bool的成员函数compare_exchange_weak/compare_exchange_strong完成compare-and-swap操作(从函数名字来看,叫做compare-exchange操作更合适)。
下面我谈谈我对这2个成员函数的理解:
compare_exchange_weak会发生佯败,原因在于它内部执行的原子化的compare-exchange操作时,无法由一条指令完成 (有些CPU不支持这种指令),所以必须改为使用多条指令,如果这些指令执行到一半,线程就被切出,就会导致compare-exchange操作失败,即使this的值与expected的值一致。发生佯败是指this的值不会被修改,以避免数据混乱。
当然啦,如果CPU的核比较多,多于当前的线程数,那么线程在执行这多条指令时自然不会有被换出的风险,此时是不会发生佯败的。
那么为什么compare_exchange_weak总是和循环配合使用呢?咱们既然使用compare_exchange_weak,目的就是希望当this的值于期望值expected的值一样时,把一个既定的值写入this中。 佯败给我们带来的影响就是,this确实和expected一样了,但是this并没有被修改为既定的值。 使用循环的目的,就在于发生佯败时,继续尝试修改*this,如下:
bool expected = false;
extern atomic<bool> b;
while(!b.compare_exchange_weak(expected,true) && !expected);
在上面的代码中,我们考虑发生佯败的情况,此时this的值应该和expected一样,均为false。发生佯败时,compare_exchange_weak返回false,expected被写入this的值,即false,等于说expected的值没变,这也只有在发生佯败时才会发生。!expected就利用了这一点,从而在发生佯败时,继续尝试修改this。(如果不是佯败而是确实this和expected不一致呢?那就不管了呗,此时也会因为expected被修改为true而跳出循环,这也是compare_exchange_weak存在的原因,要想不断尝试直至修改,应该使用compare_exchange_strong)
好吧,上面提到的佯败其实下面跟实现自旋锁的关系并不大,只是我正好学到这个compare_exchange_weak了,就多说了一点。
这里实现自旋锁之所以使用compare_exchange_weak (而不是compare_exchange_strong) 也是因为它本身就要配合循环使用,本身这个词其实很恰当,我指的是如果你想通过compare_exchange_weak实现不断重试直至成功的效果的话,就得用循环。如果在实现自旋锁时使用compare_exchange_strong,那就会造成双重循环,因为compare_exchange_strong内部自带了一个循环 (以不断尝试compare_exchange操作直至成功),这会降低性能。
下面的自旋锁,本质上靠着while循环+compare_exchange_weak不断尝试修改this为true实现的 (修改成功则compare_exchange_weak函数返回true) 。compare_exchange_weak保障了CAS操作的原子性,compare_exchange_weak必须与循环搭配使用来保证在失败的时候重试CAS操作。当compare_exchange执行失败时,可以理解为获取锁失败,此时的this应该是true,按照函数compare_exchange_weak的实现,当函数写入失败时,*this的值会写入expected中以保证下次尝试写入成功(当然这建立在你对compare_exchange_weak使用了循环的前提下),因为咱实现的是自旋锁,可不能让它尝试获取2次就成功了,因此在循环体内要把expected重新置为false。compared_exchange_weak在修改成功时返回true, 失败时返回false
class Spinlock {
public:
Spinlock() : flag(false) {}
void lock() {
bool expected = false;
while (!flag.compare_exchange_weak(expected, true, std::memory_order_acquire)) {
expected = false;
}
}
void unlock() {
flag.store(false, std::memory_order_release);
}
private:
std::atomic<bool> flag;
};
class Spinlock_Guard {
private:
Spinlock& lock;
public:
explicit Spinlock_Guard(Spinlock& l) : lock(l) {
lock.lock();
}
~Spinlock_Guard() {
lock.unlock();
}
// 禁止拷贝和赋值
Spinlock_Guard(const Spinlock_Guard&) = delete;
Spinlock_Guard& operator=(const Spinlock_Guard&) = delete;
};
使用:
Spinlock spinlock;
void task() {
Spinlock_Guard guard(spinlock);
// 执行临界区代码
std::cout << "Thread " << std::this_thread::get_id() << " is running\n";
// guard在离开作用域时自动调用析构函数,释放锁
}
防止多线程重入
std::mutex mut;
void newthread()
{
std::unique_lock<std::mutex> myuq_lock(mut, std::defer_lock);
if (!myuq_lock.try_lock()) {
// 未获取到锁
return;
}
}
或:
std::mutex mut;
std::unique_lock<std::mutex> sbguard(mut,std::try_to_lock);
if(sbguard.owns_lock())
{
//条件成立表示拿到了锁
}
else
{
//没拿到锁
}
计数器
多线程调用过程中,在对象退出时要等待处理函数完成。
#include <atomic>
class AutoIncDec {
public:
// 构造函数接受一个std::atomic<long>的引用
AutoIncDec(std::atomic<long>& v) : m_v(v) {
m_v.fetch_add(1, std::memory_order_relaxed); // 原子地增加m_v指向的值
}
// 析构函数原子地减少m_v指向的值
~AutoIncDec() {
m_v.fetch_sub(1, std::memory_order_relaxed); // 原子地减少m_v指向的值
}
private:
std::atomic<long>& m_v; // 引用一个原子类型的变量
};
使用:
std::atomic<long> counter(0);
// 函数前加上自动计数器
{
AutoIncDec incDec(counter); // counter的值被原子地增加1
// ... 在这个作用域内,counter的值保持不变
} // incDec对象被销毁,counter的值被原子地减少1
// 退出时等待任务结束
{
// 等待所有处理操作完成
while (counter.load() != 0)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
线程池
ThreadPool.h
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
class ThreadPool {
public:
ThreadPool(size_t);
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
~ThreadPool();
private:
// need to keep track of threads so we can join them
std::vector< std::thread > workers;
// the task queue
std::queue< std::function<void()> > tasks;
// synchronization
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
: stop(false)
{
for(size_t i = 0;i<threads;++i)
workers.emplace_back(
[this]
{
for(;;)
{
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();
}
}
);
}
// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::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);
// don't allow enqueueing after stopping the pool
if(stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers)
worker.join();
}
#endif
使用:
// 定义一个函数来执行任务
int taskFunction(int i) {
std::cout << "\n" << std::this_thread::get_id() << " hello " << i << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "\n" << " world " << i << std::endl;
return i * i;
}
{
// 创建一个包含4个线程的线程池
ThreadPool pool(4);
// 创建一个用于存储任务结果的向量
std::vector< std::future<int> > results;
// 向线程池中添加8个任务
for(int i = 0; i < 8; ++i) {
results.emplace_back(pool.enqueue(taskFunction, i));
}
// 获取并输出每个任务的结果
for(auto && result: results)
{
//std::cout << result.get() << "\n";
if (result.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout) {
std::cout << "The async task is still running..." << std::endl;
} else {
std::cout << "The async task has completed." << std::endl;
// 获取异步任务的结果
int value = result.get(); // 这里不会阻塞,因为任务已经完成
std::cout << "The result is: " << value << std::endl;
}
}
//std::cout << result.get() << "\n";
std::cout << std::endl;
}
启动异步任务
// 启动异步任务
std::future<int> result = std::async(std::launch::async, asyncFunction);
// ... 做其他事情 ...
// 检查任务是否完成,等待最多1秒
if (result.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout) {
std::cout << "The async task is still running..." << std::endl;
} else {
std::cout << "The async task has completed." << std::endl;
// 获取异步任务的结果
int value = result.get(); // 这里不会阻塞,因为任务已经完成
std::cout << "The result is: " << value << std::endl;
}
// 获取异步任务的结果
try {
int value = result.get(); // 这里会等待异步任务完成
std::cout << "The result is: " << value << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception occurred: " << e.what() << std::endl;
}