当前位置:主页 > 软件编程 > C代码 >

浅谈C++11中的几种锁

时间:2023-01-28 10:34:11 | 栏目:C代码 | 点击:

互斥锁(mutex)

可以避免多个线程在某一时刻同时操作一个共享资源,标准C++库提供了std::unique_lock类模板,实现了互斥锁的RAII惯用语法:
eg:

std::unique_lock<std::mutex> lk(mtx_sync_);

条件锁(condition_variable)

条件锁就是所谓的条件变量,某一个线程因为某个条件未满足时可以使用条件变量使该程序处于阻塞状态。一旦条件满足了,即可唤醒该线程(常和互斥锁配合使用),唤醒后,需要检查变量,避免虚假唤醒。
eg:

线程1:

// wait ack
{
    std::unique_lock<std::mutex> lk(mtx_sync_);
    if (!cv_sync_.wait_for(lk, 1000ms, [this](){return sync_; })) // wait for 1s
    {
        // wait failed
        printf("wait for notify timeout [%d].\n", cnt);
        return false;
    }
    else
    {
        return true;
    }
}

线程2:

{
    std::unique_lock<std::mutex> lk(mtx_sync_);
    sync_ = true;
}
// 通知前解锁,可以避免唤醒线程后由于互斥锁的关系又进入了阻塞阶段
cv_sync_.notify_one();

自旋锁(不推荐使用)

自旋锁是一种基础的同步原语,用于保障对共享数据的互斥访问。与互斥锁的相比,在获取锁失败的时候不会使得线程阻塞而是一直自旋尝试获取锁。当线程等待自旋锁的时候,CPU不能做其他事情,而是一直处于轮询忙等的状态。自旋锁主要适用于被持有时间短,线程不希望在重新调度上花过多时间的情况。实际上许多其他类型的锁在底层使用了自旋锁实现,例如多数互斥锁在试图获取锁的时候会先自旋一小段时间,然后才会休眠。如果在持锁时间很长的场景下使用自旋锁,则会导致CPU在这个线程的时间片用尽之前一直消耗在无意义的忙等上,造成计算资源的浪费。

// 用户空间用 atomic_flag 实现自旋互斥
#include <thread>
#include <vector>
#include <iostream>
#include <atomic>
 
std::atomic_flag lock = ATOMIC_FLAG_INIT;
 
void f(int n)
{
    for (int cnt = 0; cnt < 100; ++cnt) {
        while (lock.test_and_set(std::memory_order_acquire))  // 获得锁
             ; // 自旋
        std::cout << "Output from thread " << n << '\n';
        lock.clear(std::memory_order_release);               // 释放锁
    }
}
 
int main()
{
    std::vector<std::thread> v;
    for (int n = 0; n < 10; ++n) {
        v.emplace_back(f, n);
    }
    for (auto& t : v) {
        t.join();
    }
}

递归锁(recursive_mutex)

recursive_mutex 类是同步原语,能用于保护共享数据免受从个多线程同时访问。

recursive_mutex 提供排他性递归所有权语义:

您可能感兴趣的文章:

相关文章