C++线程同步

C++ 标准库提供了如下线程同步机制:

  • 互斥量(支持超时加锁、递归加锁)
  • 读写锁(共享互斥量,也支持超时加锁)
  • 互斥量包装器(基于 RAII 的思想)
  • 条件变量
  • 信号量(二元信号量、计数信号量)
  • 栅栏(支持重用)
  • 调用一次

1. 互斥量

#include <mutex>
  • mutex:提供基础的互斥功能。

    std::mutex mtx;
    
    mtx.lock();					  // locks the mutex, blocks if the mutex is not available
    bool ok = mtx.try_lock();		// tries to lock the mutex, returns if the mutex is not available 
    mtx.unlock();		  		  // unlocks the mutex 
    
  • timed_mutex:在 mutex 的基础上增加了超时加锁的功能。

    #include <chrono>
    
    using namespace std::chrono_literals;
    
    std::timed_mutex mtx;
    
    // 以相对时间的形式指定超时时间
    if (mtx.try_lock_for(100ms))
    {
        // 已加锁
    }
    
    // 以绝对时间的形式指定超时时间
    auto now = std::chrono::steady_clock::now();
    if (mtx.try_lock_until(now + 10s))
    {
    	// 已加锁
    }
    
  • recursive_mutex:在 mutex 的基础上增加了递归加锁的功能(此时,lock() 函数可以被同一线程在不释放锁的情况下多次调用)。

    std::recursive_mutex mtx;
    
    void fun1() {
    	mtx.lock();
    	// ...
    	mtx.unlock();
    }
    
    void fun2() {
    	mtx.lock();
    	// ...
    	fun1(); // recursive lock becomes useful here
    	mtx.unlock();
    };
    
  • recursive_timed_mutex:在 timed_mutex 的基础上增加了递归加锁的功能。

2. 读写锁

#include <shared_mutex>
  • shared_mutex

    std::shared_mutex mtx;
    
    // 写者(互斥)
    mtx.lock();
    bool ok = mtx.try_lock();
    mtx.unlock();
    
    // 读者(共享)
    mtx.lock_shared();
    bool ok = mtx.try_lock_shared();
    mtx.unlock_shared();
    
  • shared_timed_mutex:在 shared_mutex 的基础上增加了超时加锁的功能。

    #include <chrono>
    
    using namespace std::chrono_literals;
    
    std::shared_timed_mutex mtx;
    
    /*********************************** 写者 ***********************************/
    
    // 以相对时间的形式指定超时时间
    if (mtx.try_lock_for(100ms))
    {
        // 已加锁
    }
    
    // 以绝对时间的形式指定超时时间
    auto now = std::chrono::steady_clock::now();
    if (mtx.try_lock_until(now + 10s))
    {
    	// 已加锁
    }
    
    
    /*********************************** 读者 ***********************************/
    
    // 以相对时间的形式指定超时时间
    if (mtx.try_lock_shared_for(100ms))
    {
        // 已加锁
    }
    
    // 以绝对时间的形式指定超时时间
    auto now = std::chrono::steady_clock::now();
    if (mtx.try_lock_shared_until(now + 10s))
    {
    	// 已加锁
    }
    

3. 互斥量包装器

  • lock_guard:使用了 RAII 的机制,构造时加锁,析构时解锁。

    #include <mutex>
    
    std::mutex mtx;
    
    void f()
    {
    	const std::lock_guard<std::mutex> lock(mtx);
        // ...
        // mtx is automatically released when lock goes out of scope
    }
    
  • scoped_lock:类似于 lock_guard,但可以管理多个互斥量(可以防止死锁)。

    #include <mutex>
    
    std::mutex mtx1, mtx2;
    
    void f()
    {
        const std::scoped_lock<std::mutex, std::mutex> lock(mtx1, mtx2);
        // ...
    }
    
  • unique_lock:类似于 lock_guard,但支持延迟加锁、超时加锁、递归加锁。

    #include <mutex>
    #include <chrono>
    
    using namespace std::chrono_literals;
    
    std::timed_mutex mtx;
    
    // 构造时加锁
    std::unique_lock<std::timed_mutex> lock(mtx);
    
    // 延迟加锁:先不加锁
    std::unique_lock<std::timed_mutex> lock(mtx, std::defer_lock);
    
    lock.lock();
    bool ok = lock.try_lock();
    lock.unlock();
    
    bool ok = lock.try_lock_for(100ms);
    
    auto now = std::chrono::steady_clock::now();
    bool ok = mtx.try_lock_until(now + 10s);
    
  • shared_lock:用于管理读写锁中的读者模式(写者模式使用 unique_lock 即可),支持延迟加锁、超时加锁。

    #include <mutex>
    #include <chrono>
    
    using namespace std::chrono_literals;
    
    std::shared_mutex mtx;
    
    // 构造时加锁
    std::shared_lock<std::shared_mutex> lock(mtx);
    
    // 延迟加锁:先不加锁
    std::shared_lock<std::shared_mutex> lock(mtx, std::defer_lock);
    
    lock.lock();
    bool ok = lock.try_lock();
    lock.unlock();
    
    bool ok = lock.try_lock_for(100ms);
    
    auto now = std::chrono::steady_clock::now();
    bool ok = mtx.try_lock_until(now + 10s);
    

4. 条件变量

#include <condition_variable>
  • condition_variable:等待时只能使用 std::unique_lock<std::mutex>

    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;
    
    void worker_thread()
    {
    	/*
    	等待,直到 ready 为 true,等价于
    	while (!ready)
    	{
    		cv.wait(lock);
    	}
    	*/
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{return ready;});
     
        // after the wait, we own the lock.
        // ...
     
        // 在唤醒之前解锁,以免被唤醒的线程仍阻塞于互斥量
        lock.unlock();
        cv.notify_one();
    }
    
    #include <chrono>
    
    using namespace std::chrono_literals;
    
    std::mutex mtx;
    std::condition_variable cv;
    int i;
    
    std::unique_lock<std::mutex> lock(mtx);
    
    // 超时等待:相对时间
    if(cv.wait_for(lock, 100ms, []{return i == 1;}))
    {
    	// 条件满足 ...
    }
    
    // 超时等待:绝对时间
    auto now = std::chrono::system_clock::now();
    if(cv.wait_until(lock, now + 100ms, [](){return i == 1;}))
    {
    	// 条件满足 ...
    }
    
    // 唤醒所有等待线程
    cv.notify_all();
    
  • condition_variable_any:与 condition_variable 类似,但可以结合其他锁使用。

5. 信号量

#include <semaphore>
  • binary_semaphore:二元信号量,其实就是计数信号量模板的特化(计数为 1)。

    std::binary_semaphore sem;
    
    sem.acquire();		// decrements the internal counter or blocks until it can
    sem.release();		// increments the internal counter and unblocks acquirers
    
    book ok = sem.try_acquire();	// tries to decrement the internal counter without blocking
    
    // 超时 acquire:相对时间
    bool ok = sem.try_acquire_for(100ms);
    
    // 超时 acquire:绝对时间
    auto now = std::chrono::system_clock::now();
    bool ok = sem.try_acquire_until(now + 100ms);
    
  • counting_semaphore:计数信号量,支持的操作同上。

    std::counting_semaphore sem(4);
    

6. 栅栏

  • latch:其内部维护着一个计数器,当计数不为 0 时,所有参与者(线程)都将阻塞在等待操作处,计数为 0 时,解除阻塞。计数器不可重置或增加,故它是一次性的,不可重用。

    #include <latch>
    
    std::latch work_done(4);
    
    work_done.count_down();			 // decrements the counter in a non-blocking manner
    work_done.wait();				   // blocks until the counter reaches zero
    bool ok = work_done.try_wait();	 // tests if the internal counter equals zero
    work_done.arrive_and_wait();	    // decrements the counter and blocks until it reaches zero
    
  • barrier:类似于 latch,它会阻塞线程直到所有参与者线程都到达一个同步点,但它是可重用的。
    一个 barrier 的生命周期包含多个阶段,每个阶段都定义了一个同步点。一个 barrier 阶段包含:

    • 期望计数(设创建时指定的计数为 n),当期望计数不为 0 时,参与者将阻塞于等待操作处;
    • 当期望计数为 0 时,会执行创建 barrier 时指定的阶段完成步骤,然后解除阻塞所有阻塞于同步点的参与者线程。
    • 当阶段完成步骤执行完成后,会重置期望计数为 n - 调用arrive_and_drop()的次数,然后开始下一个阶段。
    #include <barrier>
    
    auto on_completion = []() noexcept { 
    	// locking not needed here
    	// ...
    };
    
    std::barrier sync_point(4, on_completion);
    
    sync_point.arrive();			// arrives at barrier and decrements the expected count 
    sync_point.wait();			  // blocks at the phase synchronization point until its phase completion step is run
    sync_point.arrive_and_wait();	// arrives at barrier and decrements the expected count by one, then blocks until current phase completes
    sync_point.arrive_and_drop();	// decrements both the initial expected count for subsequent phases and the expected count for current phase by one
    

7. 执行一次

确保某个操作只被执行一次(成功执行才算),即使是多线程环境下也确保只执行一次。

#include <iostream>
#include <thread>
#include <mutex>

std::once_flag flag;

void may_throw_function(bool do_throw)
{
    if (do_throw)
    {
        std::cout << "throw: call_once will retry\n"; // this may appear more than once
        throw std::exception();
    }
    std::cout << "Didn't throw, call_once will not attempt again\n"; // guaranteed once
}

void do_once(bool do_throw)
{
    try
    {
        std::call_once(flag, may_throw_function, do_throw);
    }
    catch (...)
    {}
}

int main()
{
    std::thread t1(do_once, true);
    std::thread t2(do_once, true);
    std::thread t3(do_once, false);
    std::thread t4(do_once, true);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
}
throw: call_once will retry
throw: call_once will retry
Didn't throw, call_once will not attempt again
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中的线程同步和异步通常是通过多线程编程来实现的。 线程同步是指多个线程之间协同工作,以共同完成一项任务。在同步模式下,线程之间会相互通信,以确保它们在执行任务时保持同步。常见的同步机制包括互斥、条件变量、信号量等。 互斥是一种常用的同步机制,它用于保护共享资源不被多个线程同时访问。当一个线程占用了互斥,其他线程就无法访问该共享资源,直到该线程释放。 条件变量是一种同步机制,它允许线程在共享资源达到某种状态时才能继续执行。当线程发现共享资源的状态不符合要求时,它将进入等待状态,直到其他线程改变了共享资源的状态。 信号量是一种同步机制,它用于控制多个线程对共享资源的访问。信号量的值表示共享资源的可用数量。当一个线程需要访问共享资源时,它必须先获得一个信号量,如果信号量的值为零,线程将进入等待状态。 线程异步是指多个线程之间相互独立地执行任务,不需要进行协同工作。在异步模式下,每个线程都可以独立地执行任务,不需要等待其他线程的输入或输出结果。常见的异步机制包括消息队列、事件驱动等。 消息队列是一种异步机制,它用于在多个线程之间传递消息。当一个线程需要向另一个线程发送消息时,它将消息写入消息队列,该线程将在适当的时候读取该消息。 事件驱动是一种异步机制,它用于响应外部事件的发生。当事件发生时,系统将通知相应的线程进行处理,而不需要等待其他线程的输入或输出结果。 总之,线程同步和异步都是多线程编程中的重要概念,程序员需要根据具体的应用场景来选择合适的同步或异步机制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值