lock_guard:更加灵活的锁管理类模板,构造时是否加锁是可选的,在对象析构时如果持有锁会自动释放锁,所有权可以转移。对象生命期内允许手动加锁和释放锁。

scope_lock:严格基于作用域(scope-based)的锁管理类模板,构造时是否加锁是可选的(不加锁时假定当前线程已经获得锁的所有权),析构时自动释放锁,所有权不可转移,对象生存期内不允许手动加锁和释放锁。

share_lock:用于管理可转移和共享所有权的互斥对象。

使用std::lock_guard类模板修改前面的代码,在lck对象构造时加锁,析构时自动释放锁,即使insert抛出了异常lck对象也会被正确的析构,所以也就不会发生互斥对象没有释放锁而导致死锁的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::set<int> int_set;
std::mutex mt;
auto f = [&int_set, &mt]() {
    try {
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<> dis(1, 1000);
        for(std::size_t i = 0; i != 100000; ++i) {
            std::lock_guard<std::mutex> lck(mt);
            int_set.insert(dis(gen));
        }
    } catch(...) {}
};
std::thread td1(f), td2(f);
td1.join();
td2.join();

互斥对象管理类模板的加锁策略

前面提到std::lock_guard、std::unique_lock和std::shared_lock类模板在构造时是否加锁是可选的,C++11提供了3种加锁策略。

策略 tag type 描述
(默认) 请求锁,阻塞当前线程直到成功获得锁。
std::defer_lock std::defer_lock_t 不请求锁。
std::try_to_lock std::try_to_lock_t 尝试请求锁,但不阻塞线程,锁不可用时也会立即返回。
std::adopt_lock std::adopt_lock_t 假定当前线程已经获得互斥对象的所有权,所以不再请求锁。

下表列出了互斥对象管理类模板对各策略的支持情况。

策略 std::lock_guard std::unique_lock std::shared_lock
(默认) √(共享)
std::defer_lock ×
std::try_to_lock ×
std::adopt_lock

下面的代码中std::unique_lock指定了std::defer_lock。

1
2
3
4
5
std::mutex mt;
std::unique_lock<std::mutex> lck(mt, std::defer_lock);
assert(lck.owns_lock() == false);
lck.lock();
assert(lck.owns_lock() == true);

对多个互斥对象加锁

在某些情况下我们可能需要对多个互斥对象进行加锁,考虑下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
std::mutex mt1, mt2;
// thread 1
{
    std::lock_guard<std::mutex> lck1(mt1);
    std::lock_guard<std::mutex> lck2(mt2);
    // do something
}
// thread 2
{
    std::lock_guard<std::mutex> lck2(mt2);
    std::lock_guard<std::mutex> lck1(mt1);
    // do something
}

如果线程1执行到第5行的时候恰好线程2执行到第11行。那么就会出现

  • 线程1持有mt1并等待mt2
  • 线程2持有mt2并等待mt1

发生死锁。 为了避免发生这类死锁,对于任意两个互斥对象,在多个线程中进行加锁时应保证其先后顺序是一致。前面的代码应修改成

1
2
3
4
5
6
7
8
9
10
11
12
13
std::mutex mt1, mt2;
// thread 1
{
    std::lock_guard<std::mutex> lck1(mt1);
    std::lock_guard<std::mutex> lck2(mt2);
    // do something
}
// thread 2
{
    std::lock_guard<std::mutex> lck1(mt1);
    std::lock_guard<std::mutex> lck2(mt2);
    // do something
}

更好的做法是使用标准库中的std::lockstd::try_lock函数来对多个Lockable对象加锁。std::lock(或std::try_lock)会使用一种避免死锁的算法对多个待加锁对象进行lock操作(std::try_lock进行try_lock操作),当待加锁的对象中有不可用对象时std::lock会阻塞当前线程知道所有对象都可用(std::try_lock不会阻塞线程当有对象不可用时会释放已经加锁的其他对象并立即返回)。使用std::lock改写前面的代码,这里刻意让第6行和第13行的参数顺序不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::mutex mt1, mt2;
// thread 1
{
    std::unique_lock<std::mutex> lck1(mt1, std::defer_lock);
    std::unique_lock<std::mutex> lck2(mt2, std::defer_lock);
    std::lock(lck1, lck2);
    // do something
}
// thread 2
{
    std::unique_lock<std::mutex> lck1(mt1, std::defer_lock);
    std::unique_lock<std::mutex> lck2(mt2, std::defer_lock);
    std::lock(lck2, lck1);
    // do something
}

此外std::lock和std::try_lock还是异常安全的函数(要求待加锁的对象unlock操作不允许抛出异常),当对多个对象加锁时,其中如果有某个对象在lock或try_lock时抛出异常,std::lock或std::try_lock会捕获这个异常并将之前已经加锁的对象逐个执行unlock操作,然后重新抛出这个异常(异常中立)。并且std::lock_guard的构造函数lock_guard(mutex_type& m, std::adopt_lock_t t)也不会抛出异常。所以std::lock像下面这么用也是正确

1
2
3
std::lock(mt1, mt2);
std::lock_guard<std::mutex> lck1(mt1, std::adopt_lock);
std::lock_guard<std::mutex> lck2(mt2, std::adopt_lock);

scope_lock与lock_guard区别的更多相关文章

  1. C++ 并发编程,std::unique_lock与std::lock_guard区别示例

    背景 平时看代码时,也会使用到std::lock_guard,但是std::unique_lock用的比较少.在看并发编程,这里总结一下.方便后续使用. std::unique_lock也可以提供自动 ...

  2. C++11 std::unique_lock与std::lock_guard区别及多线程应用实例

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为.通常的做法是在修改共享数据成员的时候进行加锁--mutex.在使用锁的时候通 ...

  3. std::unique_lock与std::lock_guard区别示例

    std::lock_guard std::lock_guard<std::mutex> lk(frame_mutex); std::unique_lock<std::mutex> ...

  4. std::unique_lock与std::lock_guard分析

    背景 C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢,导致程序出现未定义或异常行为.通常的做法是在修改共享数据成员时进行加锁(mutex).在使用锁 ...

  5. C++ 11 多线程下std::unique_lock与std::lock_guard的区别和用法

    这里主要介绍std::unique_lock与std::lock_guard的区别用法 先说简单的 一.std::lock_guard的用法 std::lock_guard其实就是简单的RAII封装, ...

  6. std::unique_lock<std::mutex> or std::lock_guard<std::mutex> C++11 区别

    http://stackoverflow.com/questions/20516773/stdunique-lockstdmutex-or-stdlock-guardstdmutex The diff ...

  7. boost::unique_lock和boost::lock_guard的区别

    lock_guard unique_lock boost::mutex mutex; boost::unique_lock<boost::mutex> lock(mutex); std:: ...

  8. C++11中lock_guard和unique_lock的区别

    c++11中有一个区域锁lock_guard,还有第二个区域锁unique_lock. 区域锁lock_guard使用起来比较简单,除了构造函数外没有其他member function,在整个区域都有 ...

  9. std::lock_guard和std::unique_lock的区别

    std::lock_guard 1 初始化的时候锁定std::mutex std::mutex m_mtx; std::lock_guard<std::mutex> m_lock(m_mt ...

随机推荐

  1. UE4 径向模糊radiu blur

    hlsl代码为: float2 ScreenMult = ; ; ] = {-0.08,-0.05,-0.03,-0.02,-0.01,0.01,0.02,0.03,0.05,0.08}; float ...

  2. 记录idea maven项目打包部署web项目mapper扫描失败

    最开始以为这里出了问题,后来加上以后还是不能把mapper.xml打包进去 这是报的异常信息 Mybatis启动老是报绑定错误(找不到Mapper对应的 SQL配置),经过一番Google未能解决问题 ...

  3. WEBZIP为什么打不开网页

    先试三个办法 一.打开IE,点工具,点internet选项,点高级,点恢复默认设置,点保存,退出,重新打开IE 二.打开IE,刷新五次以上 三.打开IE,点工具,点internet选项,点删除文件,点 ...

  4. 织梦dedecsm系统"企业简介"类单栏目模版如何修改和调用

    2013-1-12 14:46 | 发布者: moke | 栏目:dedecms教程        我们的模版里应该都有article_article.htm这个模版,这个模版是文章内容页模板,也就是 ...

  5. cuda纹理内存的使用

    CUDA纹理内存的访问速度比全局内存要快,因此处理图像数据时,使用纹理内存是一个提升性能的好方法. 贴一段自己写的简单的实现两幅图像加权和的代码,使用纹理内存实现. 输入:两幅图 lena, moon ...

  6. servlet多线程问题

    Servlet本身是单实例的,这样当多个用户同时访问某个Servlet时,会访问该唯一的Servlet实例中的成员变量,如果对成员变量进行写入工作,那就会导致Servlet的多线程问题,即数据不一致. ...

  7. WinForm中,设置不显示窗口的标题栏

    1:ControlBox设置False,然后标题为""的时候标题就不显示了2:把窗体设置为无边的窗体 FormBoderStyle 设为 None  (在Mdi中,关闭按钮会还在) ...

  8. diffMerge安装配置使用

    概述: 在用git进行源代码版本维护的时候,常常会进行各代码版本之前区别的查看,例如在每次提交改动前进行git diff 可以看到源文件代码相对相应版本或是远程仓库的改动情况,如果有冲突还需要进行me ...

  9. 通过psping测试结果,初步判断远端服务器的状态

    1.psping的输出结果为如下正常显示时,说明远端服务器的IP及端口可用 C:\Users\he.liming>psping 139.219.66.205:4352 PsPing v2.10 ...

  10. Windows核心编程&进程

    1. 进程的定义 说白了进程就是一个正在运行的执行程序,包含内核对象和独立的地址空间,内核对象负责统计和管理进程信息,地址空间包括所有可执行文件或DLL 模块的代码和数据.动态内存分配(线程堆和栈的分 ...