Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/xslt/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 防止代码死锁的锁定策略和技术_C++_Design Patterns_Locking_Deadlock - Fatal编程技术网

C++ 防止代码死锁的锁定策略和技术

C++ 防止代码死锁的锁定策略和技术,c++,design-patterns,locking,deadlock,C++,Design Patterns,Locking,Deadlock,防止代码死锁的常见解决方案是确保锁定序列以常见方式发生,而不管哪个线程正在访问资源 例如,给定线程T1和T2,其中T1访问资源A,然后B和T2访问资源B,然后A。按需要的顺序锁定资源会导致死锁。简单的解决方案是先锁定A,然后锁定B,而不管特定于顺序的线程将使用哪些资源 问题情况: Thread1 Thread2 ------- ------- Lock Resource A

防止代码死锁的常见解决方案是确保锁定序列以常见方式发生,而不管哪个线程正在访问资源

例如,给定线程T1和T2,其中T1访问资源A,然后B和T2访问资源B,然后A。按需要的顺序锁定资源会导致死锁。简单的解决方案是先锁定A,然后锁定B,而不管特定于顺序的线程将使用哪些资源

问题情况:

Thread1                         Thread2
-------                         -------
Lock Resource A                 Lock Resource B
 Do Resource A thing...          Do Resource B thing...
Lock Resource B                 Lock Resource A
 Do Resource B thing...          Do Resource A thing...
可能的解决办法:

Thread1                         Thread2
-------                         -------
Lock Resource A                 Lock Resource A
Lock Resource B                 Lock Resource B
 Do Resource A thing...          Do Resource B thing...
 Do Resource B thing...          Do Resource A thing...

我的问题是,在编码中使用了哪些其他技术、模式或常见做法来保证防止死锁?

在避免死锁时,一致的锁定顺序几乎是第一个也是最后一个字

有一些相关的技术,比如无锁编程(没有线程等待锁,因此不可能有循环),但这实际上只是“避免不一致的锁顺序”规则的一个特例——即,它们通过避免所有锁来避免不一致的锁。不幸的是,无锁编程有它自己的问题,所以它也不是万灵药

如果您想稍微扩大范围,有一些方法可以在死锁发生时检测死锁(如果出于某种原因您无法设计程序来避免死锁),也有一些方法可以在死锁发生时打破死锁(例如,总是通过超时锁定,或通过强制死锁线程之一拥有锁()命令失败,甚至只是通过杀死一个死锁线程);但我认为,它们都不如一开始就确保死锁不会发生


(顺便说一句,如果你想用一种自动化的方法来检查你的程序是否存在潜在的死锁,请查看valgrind的helgrind工具。它将监视你的代码的锁定模式,并通知你任何不一致的地方——非常有用)

您描述的技术不仅仅是常见的:它是一种一直被证明有效的技术。但是,在C++中对线程代码进行编码时,还有一些其他的规则,其中最重要的可能是:


  • 调用虚拟函数时不要持有锁:即使在编写代码时,您知道将调用哪个函数以及它将执行什么操作,代码将不断演化,虚拟函数将被覆盖,因此最终,您也不知道它将执行什么操作以及是否需要任何其他锁,这意味着您将失去锁定的保证顺序
  • <>强>注意种族条件<强>:在C++中,没有什么可以告诉你在线程之间共享给定数据时,你不需要在它上使用某种同步。一个例子是在几天前,卢克在这个C++聊天室发布的,作为例子(在这篇文章的末尾的代码):仅仅尝试同步发生在附近的其他事情并不意味着你的代码被正确地同步。
  • 尝试隐藏异步行为:通常最好将并发性隐藏在软件的体系结构中,这样大多数调用代码就不会关心是否存在线程。它使体系结构更易于使用——特别是对于一些不习惯并发的人来说
我可以继续讲一段时间,但根据我的经验,使用线程的最简单方法是使用可能使用代码的每个人都熟悉的模式,例如生产者/消费者模式:这很容易解释,而且您只需要一个工具(队列)就可以让线程彼此通信。毕竟,两个线程相互同步的唯一原因是允许它们通信

更一般的建议:

  • 在你有过使用锁进行并发编程的经验之前,不要尝试无锁编程——这是一种很容易让你大发雷霆的方法,或者会遇到非常奇怪的bug
  • 将共享变量的数量和访问这些变量的次数降至最低
  • 不要指望两个事件总是以相同的顺序发生,即使你看不到它们有任何相反的顺序
  • 更一般地说:不要指望时机——不要认为一个给定的任务总是需要一定的时间
以下代码将失败:
#包括
#包括
#包括
#包括
#包括
无效的
没有什么可能出错的
{
int标志=0;
std::条件变量cond;
std::互斥互斥;
int done=0;
typedef std::唯一锁;
自动常数f=[&]
{
如果(标志==0)+标志;
锁l(互斥锁);
++完成;
第二,通知某人;
};
标准::螺纹螺纹[2]={
标准:螺纹(f),
标准:螺纹(f)
};
线程[0]。连接();
线程[1]。连接();
锁l(互斥锁);
cond.wait(l,[done]{return done==2;});
//这当然不会失败!
断言(标志==1);
}
int
main()
{
因为没有什么事是不可能出错的;
}

安德烈·亚历山德雷斯库(Andrei Alexandrescu)虽然不是您提到的已知序列解决方案的替代方案,但他写了一些编译时检查的技术,以确保锁的获取是通过预期的机制完成的。另一种技术是事务性编程。但这种情况并不常见,因为它通常涉及专门的硬件(目前大部分仅在研究机构中)

每个资源跟踪来自不同线程的修改。将更改提交给所有资源(它正在使用)的第一个线程将赢得所有其他线程(使用这些资源)并回滚,以便在资源处于新提交状态时重试


阅读本主题的一个过于简单的起点是。

您询问的是设计级别,但我将添加一些较低级别的编程实践

#include <thread>
#include <cassert>
#include <chrono>
#include <iostream>
#include <mutex>
 
void
nothing_could_possibly_go_wrong()
{
    int flag = 0;
 
    std::condition_variable cond;
    std::mutex mutex;
    int done = 0;
    typedef std::unique_lock<std::mutex> lock;
 
    auto const f = [&]
    {
        if(flag == 0) ++flag;
        lock l(mutex);
        ++done;
        cond.notify_one();
    };
    std::thread threads[2] = {
        std::thread(f),
        std::thread(f)
    };
    threads[0].join();
    threads[1].join();
 
    lock l(mutex);
    cond.wait(l, [done] { return done == 2; });
 
    // surely this can't fail!
    assert( flag == 1 );
}
 
int
main()
{
    for(;;) nothing_could_possibly_go_wrong();
}