• 点击查看原文 Prefer Locks to Mutexes

If the previous post showed something, it’s, that you should use mutexes with great care. That’s why you should wrap them in a lock.

  • 上篇说明了需要特别小心使用 mutex (否则容易出现死锁问题)。我们应该通过 lock 使用 mutex。

Locks

Locks take care of thier resource following the RAII idiom. A lock automatically binds its mutex in the constructor and releases it in the destructor. This considerably reduces risk of a deadlock, because the runtime takes care of the mutex.
Locks are available in two flavours in C++11. std::lock_guard for the simple, and std::unique-lock for the advanced use case.

  • locks 按照 RAII 习惯用法来处理资源。锁在构造函数中自动绑定 mutex,并在析构函数中释放它。这大大降低了死锁的风险,因为运行时负责处理 mutex。
  • 在c++ 11中有两种锁可供选择。简单用例为 std::lock_guard,高级用例为 std::unique-lock。
std::lock_guard
  • First, the simple use case.

  • 首先是简单用法。

    mutex m;
    m.lock();
    sharedVariable= getVar();
    m.unlock();
    

With so little code mutex m ensures access of the critical section sharedVariable= getVar() is sequential. Sequential means - in this special case - that each thread gains acces to critical section in order. The code is simple, but prone to deadlocks. Deadlock appears if the critical section throws an exception or if the programmer simply forgets to unlock the mutex. With std::lock_guard we can do this more elegant:

  • 仅需要如此少的代码,mutex m 确保了对临界区 sharedVariable= getVar() 的访问是顺序的。在这种特殊情况下,Sequential 意思是每个线程依次进入临界区。代码很简单,但是被证明可能产生死锁。如果临界区抛出异常,或者程序员忘记对 mutex 进行解锁,就会出现死锁。使用 std::lock_guard 我们可以做得更优雅:

    {std::mutex m,std::lock_guard<std::mutex> lockGuard(m);sharedVariable= getVar();
    }
    

That was easy. But what’s about the opening and closing brackets? The lifetime of std::lock_guard is limited by the brackets (http://en.cppreference.com/w/cpp/language/scope#Block_scope). That means, its lifetime ends when it leaves the critical section. Exactly at that time point, the destructor of std::lock_guard is called and - I guess, you know it - the mutex is released. It happens automatically, and, in addition, it happens if getVar() in sharedVariable = getVar() throws an exception. Of course, function body scope or loop scope also limit the lifetime of an object.

  • 这很容易。但是开始和结束括号是什么意思? std::lock_guard 的生命周期受到括号的限制 (http://en.cppreference.com/w/cpp/language/scope#Block_scope) 。这意味着,当它离开临界区时,它的生命周期就结束了。就在那个时候,std::lock_guard 的析构函数被调用,并且——我猜,你知道—— mutex 被释放。它会自动发生,而且,如果 sharedVariable = getVar() 中的 getVar() 抛出异常,它也会发生。当然,函数体作用域或循环作用域也限制了对象的生命周期。
std::unique_lock

std::unique_lock is mightier but more expansive than its small brother std::lock_guard.
A std::unique_lock enables you in addition to std::lock_guard

  • create it without an associated mutex
  • create it without a locked associated mutex
  • explicitly and repeatedly set or release the lock of the associated mutex
  • move the mutex
  • try to lock the mutex
  • delayed lock the associated mutex

But why is it necessary? Remember the deadlock from the post Risks of mutexes? The reason for the deadlock was the mutexes were locked in different sequence.

  • unique_lock 比它的兄弟 std::lock_guard 更强大,也更具有扩展性。

  • 一个std::unique_lock 使你除了std::lock_guard 的功能,还有

    • 创建它时不需要关联 mutex
    • 创建它时不需要锁定关联的 mutex
    • 显式和重复地设置或释放关联的 mutex
    • 移动 mutex
    • 尝试锁定 mutex
    • 延迟锁定关联的 mutex
  • 但为什么这是必要的呢?还记得上一篇 Risks of mutexes 中的死锁吗?死锁的原因是 mutex 在不同的线程中被锁定。

    // deadlock.cpp#include <iostream>
    #include <chrono>
    #include <mutex>
    #include <thread>struct CriticalData{std::mutex mut;
    };void deadLock(CriticalData& a, CriticalData& b){a.mut.lock();std::cout << "get the first mutex" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(1));b.mut.lock();std::cout << "get the second mutex" << std::endl;// do something with a and ba.mut.unlock();b.mut.unlock();}int main(){CriticalData c1;CriticalData c2;std::thread t1([&]{deadLock(c1,c2);});std::thread t2([&]{deadLock(c2,c1);});t1.join();t2.join();}
    

The solution is easy. The function deadlock has to lock their mutex in an atomic fashion. That’s exactly what happens in the following example.

  • 解决办法很简单。函数 deadlock 必须以原子方式锁定它们的 mutex 。下面的例子就是这样的。

    // deadlockResolved.cpp#include <iostream>
    #include <chrono>
    #include <mutex>
    #include <thread>struct CriticalData{std::mutex mut;
    };void deadLock(CriticalData& a, CriticalData& b){std::unique_lock<std::mutex>guard1(a.mut,std::defer_lock);std::cout << "Thread: " << std::this_thread::get_id() << " first mutex" <<  std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(1));std::unique_lock<std::mutex>guard2(b.mut,std::defer_lock);std::cout << "    Thread: " << std::this_thread::get_id() << " second mutex" <<  std::endl;std::cout << "        Thread: " << std::this_thread::get_id() << " get both mutex" << std::endl;std::lock(guard1,guard2);// do something with a and b
    }int main(){std::cout << std::endl;CriticalData c1;CriticalData c2;std::thread t1([&]{deadLock(c1,c2);});std::thread t2([&]{deadLock(c2,c1);});t1.join();t2.join();std::cout << std::endl;}
    

In case you call the constructor of std::unique_lock with the argument std::defer_lock, the lock will not be locked automatically. It happens in line 14 and 19. The lock operation is performed atomically in line 23 by using the variadic template std::lock. A variadic template is a template which can accept an arbitrary number of arguments. Here, the arguments are locks. std::lock tries to get the all locks in an atomic step. So, he fails or gets all of them.

  • 如果你使用参数 std::defer_lock 调用 std::unique_lock 的构造函数,那么锁将不会自动锁定。它发生在第14和19行。通过使用可变参数模板 std::lock,在第23行原子地执行锁操作。可变参数模板是一种可以接受任意数量参数的模板。这里的参数是锁。std::lock 尝试在一个原子步骤中获取所有的锁。所以,他要么失败了,要么得到了。

In this example, std::unique_lock takes care of the lifetime of the resources, std::lock locks the associated mutex. But, you can do it the other way around. In the first step you lock the mutexes, in the second std::unique_lock takes care of the lifetime of resources. Here is a sketch of the second approach.

  • 在这个例子中,std::unique_lock 负责资源的生命周期,std::lock 锁定关联的 mutex。但是,你可以反过来做。在第一步中锁定 mutex,在第二步 std::unique_lock 处理资源的生命周期。下面是第二种方法的概述。

    std::lock(a.mut, b.mut);
    std::lock_guard<std::mutex> guard1(a.mut, std::adopt_lock);
    std::lock_guard<std::mutex> guard2(b.mut, std::adopt_lock);
    

Now, all is fine. The program runs without deadlock.

  • 现在,一切都好了。程序运行时没有死锁。
A side note: Special deadlocks

It’s an illusion that only a mutex can produce a deadlock. Each time a thread has to wait for a resource, while it is holding a resource, a deadlock lurks near.
Even a thread is a resource.

  • 认为只有 mutex 才能产生死锁是一种错觉。每当一个线程需要等待一个资源且此时正持有一个资源时,一个死锁就会在附近潜伏着。
  • 线程也是资源。
    // blockJoin.cpp#include <iostream>
    #include <mutex>
    #include <thread>std::mutex coutMutex;int main(){std::thread t([]{std::cout << "Still waiting ..." << std::endl;std::lock_guard<std::mutex> lockGuard(coutMutex);std::cout << std::this_thread::get_id() << std::endl;});{std::lock_guard<std::mutex> lockGuard(coutMutex);std::cout << std::this_thread::get_id() << std::endl;t.join();}}
    

The program immediately stands still.

  • 程序立刻不动了。

What’s happening? The lock of output stream std::cout and the waiting of the main thread for its child t are the cause for the deadlock. By observing the output, you can easily see, in which order the statements will be performed.

  • 发生了什么? 输出流 std::cout 的锁定和主线程对其子线程 t 的等待是死锁的原因。通过观察输出,你可以很容易地看到语句的执行顺序。

In the first step, the main thread executes the lines 19 - 21. It waits in line 21 by using the call t.join(), until its child t is done with its work package. The main thread is waiting, while it is locks the output stream. But that’s exactly the resource the child is waiting for. Two ways to solve this deadlock come to mind.

  • 在第一步中,主线程执行第19 - 21行。它在第21行中调用 t.join() 等待,直到它的子线程 t 完成它的工作任务。主线程正在等待,而它锁定了输出流。但这正是子线程正在等待的资源。有两种方法可以解决这个僵局。

    • The main thread locks the output stream std::cout after the call t.join().
    • 主线程在调用 t.join() 后锁定输出流 std::cout。

      {t.join();std::lock_guard<std::mutex> lockGuard(coutMutex);std::cout << std::this_thread::get_id() << std::endl;
      }
      
    • The main thread releases its lock by an additional scope. This is done before the t.join() call.
    • 主线程通过一个额外的范围释放它的锁。这是在调用 t.join() 之前完成的。

      {{std::lock_guard<std::mutex> lockGuard(coutMutex);std::cout << std::this_thread::get_id() << std::endl;}t.join();
      }
      

What’s next?

In the next post I’ll talk about reader-writer locks. Reader-writer locks empowers you since C++14, to distinguish between reading and writing threads. So, the contention on the shared variable will be mitigated, because an arbitrary number of reading threads can access the shared variable at the same time.

  • 在下一篇文章中,我将讨论读写锁。自 c++ 14 以来,读写锁使您能够区分读线程和写线程。因此,对共享变量的争夺将会减少,因为任意数量的读取线程可以同时访问共享变量。

C++11 Prefer Locks to Mutexes(译)相关推荐

  1. Python for Informatics 第11章 正则表达式五(译)

    注:文章原文为Dr. Charles Severance 的 <Python for Informatics>.文中代码用3.4版改写,并在本机测试通过. 11.4 转义字符 之前我们在正 ...

  2. 11 Java NIO Non-blocking Server-翻译

    尽管你对Java NIO的工作原理很了解,但是设计一个非阻塞的服务器仍然困难.与阻塞的IO相比,非阻塞的IO也包含一些挑战.这里将会讨论一些非阻塞服务器所面临的一些挑战,以及一些可行的方案. 查找关于 ...

  3. python for informatics_Python for Informatics 第11章 正则表达式一(译)

    注:文章原文为Dr. Charles Severance 的 <Python for Informatics>.文中代码用3.4版改写,并在本机测试通过. 目前为止,我们一直在通读文件,查 ...

  4. [译][Tkinter 教程10] Text 控件

    已获原作者授权. 原系列地址: Python Tkinter 简介及简例 Text 控件用来显示多行文本. Tkinter 的 Text 控件很强大, 很灵活, 可以实现很多功能. 虽然这个控件的主要 ...

  5. Effective C# 原则11:选择foreach循环

    Effective C# 原则11:选择foreach循环 Item 11: Prefer foreach Loops C#的foreach语句是从do,while,或者for循环语句变化而来的,它相 ...

  6. C++11 并发指南一(C++11 多线程初探)

    引言 C++11 自2011年发布以来已经快两年了,之前一直没怎么关注,直到最近几个月才看了一些 C++11 的新特性,今后几篇博客我都会写一些关于 C++11 的特性,算是记录一下自己学到的东西吧, ...

  7. [译][Tkinter 教程14] menu 菜单

    已获原作者授权. 原系列地址: Python Tkinter 简介 一提到"menu"这个词, 很多人首先想到的是餐馆里的菜单. 虽然餐馆菜单和计算机程序中的菜单看起来一点也不像, ...

  8. [译][Tkinter 教程02] Message 控件

    已获原作者授权. 原系列地址: Python Tkinter Message 控件 Message 控件用来展示一些文字短消息. Message 和 Label 控件有些类似, 但在展示文字方面比 L ...

  9. [译][Tkinter 教程15] event 事件绑定

    已获原作者授权. 原系列地址: Python Tkinter 简介 一个 Tkinter 应用生命周期中的大部分时间都处在一个消息循环 (event loop) 中. 它等待事件的发生: 事件可能是 ...

最新文章

  1. 为什么不建议用 equals 判断对象相等?
  2. python字符串命名_从输入字符串到命名复制python 2.7
  3. python入门到精通需要学多久-Python零基础入门到精通:一个月就够了
  4. poj2411 Mondriaan's Dream (状压dp+多米诺骨牌问题)
  5. 二叉树的深搜(DFS)与广搜(BFS)
  6. 25个python相关的基础概念总结
  7. 前端学习(1422):ajax获取服务器端的响应
  8. matlab建立的发动机的模型,奇瑞使用基于模型的设计实现发动机管理系统软件的自主开发...
  9. 通过八叉树进行空间分割和搜索
  10. Bailian2981 大整数加法【大数】(POJ NOI0106-10)
  11. electron打包失败在下载nsis的地方
  12. Win10环境下初始化MySQL
  13. linux中安装openoffice
  14. 将越狱进行到底 Pod2g邀约众大神组建evad3rs
  15. 桌面应用程序与web应用程序
  16. 外卖订单语音通知功能如何实现?(附外卖订单语音通知模板)
  17. 主流相声演员与郭德纲到底结了什么仇?
  18. dom对象jquery对象
  19. HEVC-I帧中CU,TU,PU之间的关系
  20. Magento2 入门指南(新手必读)

热门文章

  1. Warning: Function components cannot be given refs
  2. 手机网络邻居访问电脑_一起来看看手机如何访问电脑局域网共享的文件夹
  3. java二维数组遍历与元素查找
  4. 历届图灵奖得主及研究领域
  5. 2019java 开发工程师 最新面试官 问的问题
  6. JDK1.8+Spring5+SpringMVC5+Mybatis3.4项目(SMM框架)搭建
  7. R语言学习记录:聚类分析的R实现
  8. Altium designer18设置原理图尺寸
  9. 使用Qt获取系统版本
  10. Java Map以及HashMap、TreeMap、HashTable