c++ 多线程

读写锁

在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;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值