单线程应用程序中的死锁

Posted

技术标签:

【中文标题】单线程应用程序中的死锁【英文标题】:Deadlock in Single threaded application 【发布时间】:2010-01-29 15:32:11 【问题描述】:

单线程应用程序会出现死锁吗?如果有,请举个例子。

【问题讨论】:

【参考方案1】:

是的,如果您拥有不可重入的锁并且线程尝试重新获取它已经拥有的锁(例如通过递归调用),单线程应用程序可能会死锁。

编辑:我看到帖子被标记为“Java”;我不知道那是更新还是我之前错过了,但无论如何 Java 中的锁都是可重入的,因此您无法以这种方式死锁单线程应用程序。

【讨论】:

通常,这会导致某种错误,表明线程已经拥有锁。 那不取决于平台吗? 它可能会锁定,但根据术语的定义,这不会是死锁。诚然,由于没有更好的词,我可能会称之为僵局:) "...如果您有线程,单线程应用程序可能会死锁..." - 听起来像是一个矛盾的术语。根据定义,单线程应用程序没有线程。 @atzz:首先,单线程应用程序只有一个线程,而不是没有线程。其次,我的帖子显然是指线程是否可以重新获取特定平台中的锁。您的反对意见是不明智的。【参考方案2】:

是的,如果应用程序与另一个应用程序共享资源,它可能会死锁。

【讨论】:

好点。这是否是预期的答案取决于确切的原始措辞,但正如这里所写,这将是最好的答案。【参考方案3】:

这在一定程度上取决于您如何解释问题。如果涉及与其他应用程序共享的资源,则可以。没有共享资源,不行。

死锁是指两个或多个相互竞争的动作正在等待另一个完成,因此永远都不会。

单线程,只有一个动作,无法与自己竞争。

来自***:

必要条件

发生死锁有四个充分必要条件,称为 科夫曼条件来自 E. G. Coffman 1971 年的一篇文章中的首次描述。

    互斥条件:一次不能被多个进程使用的资源 持有和等待条件:已经持有资源的进程可能会请求新资源 无抢占条件:无法从持有它的进程中强制移除资源,只能通过进程的显式操作释放资源 循环等待条件:两个或多个进程形成一个循环链,其中每个进程等待链中下一个进程持有的资源

根据这个定义,需要两个进程来构成死锁。

【讨论】:

您的意思是“在 Java 中”,对吗?否则,MSDN 不同意您的看法。 msdn.microsoft.com/en-us/library/ms792855.aspx 我的意思是一般的计算机科学。 那个链接描述了一个可能发生的场景——你不同意吗? @danben:我不同意所描述的案例不应该被称为死锁(根据引用的定义)。因此,不提这个案子至少是可以辩护的,特别是如果有一个更简单的死锁发生的例子。 好吧,如果你坚持使用***的文章作为死锁的权威,那么你也不得不说单个多线程Java应用程序永远不会死锁,因为***声称死锁只发生在两个进程之间,并且线程不是进程。在某些时候,开始质疑你的定义可能是谨慎的。【参考方案4】:

线程 A 获取资源 1,并尝试重新获取资源 1。 自循环情况。

线程获取 lock1 -> 在临界区运行 -> 尝试获取 lock1 -> 无限等待 == 自死锁。 为了解决这个问题,你需要递归锁。

【讨论】:

【参考方案5】:

是的,单线程事件驱动(非阻塞)应用程序肯定会死锁。即使没有任何操作系统同步原语,如互斥锁,甚至根本没有操作系统。在 FSM(有限状态机)设计中,死锁是常见的错误。

假设您有一个只能通过 write(COMMAND) 然后 read(RESP) 模式访问的串行设备。所以要同时访问它你需要一些序列化技术。最简单的方法是排队。 这是 javascript 中此类队列的最简单实现:

function SharedResource() 
    var self = 
        queue_: [],
        
        acquire: function(on_sharedResource_ready) 
            var n = self.queue_.push(on_sharedResource_ready);
            if(n==1) // first task
                on_sharedResource_ready();
            
        ,
    
        release: function() 
            if(self.queue_.length >= 1) 
                // Pop current task
                self.queue_.shift();
                
                // Exec the next task
                var next = self.queue_[0];
                if(next) 
                    next();
                
            else
                throw 'SharedResource::release(): BUG - queue is empty';
            
        ,
    ;
    
    return self;

我们可以在 button_click 事件中使用它,例如:

var serialPort1 = SharedResource();
var serialPort2 = SharedResource();

function button1_on_click()
    log("button1_on_click(): Acquiring serial port 1");
    serialPort1.acquire(function()
        log("button1 Serial port 1 acquired");
        setTimeout(function()
            log("button1: Acquiring serial port 2");
            serialPort2.acquire(function()
                log("button1 Serial port 2 acquired");
                // Simulate long time work with a ports
                setTimeout(on_work_with_serial_port_done, 2000);
            );
        , 1000);
    );
    
    function on_work_with_serial_port_done()
        log("button1 Releasing serial port 2");
        serialPort2.release();
        log("button1 Releasing serial port 1");
        serialPort1.release();
    


function button2_on_click()
    log("button2_on_click(): Acquiring serial port 2");
    serialPort2.acquire(function()
        log("button2 Serial port 2 acquired");
        setTimeout(function()
            log("button2: Acquiring serial port 1");
            serialPort1.acquire(function()
                log("button2 Serial port 1 acquired");
                // Simulate long time work with a ports
                setTimeout(on_work_with_serial_port_done, 2000);
            );
        , 1000);
    );
    
    function on_work_with_serial_port_done()
        log("button2 Releasing serial port 1");
        serialPort1.release();
        log("button2 Releasing serial port 2");
        serialPort2.release();
    

以及让它工作的其余代码:

function getTime()
    var today = new Date();
    var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
    return time;


// simple logger
function log(text)
    var logdiv = document.getElementById('log');
    var br = document.createElement("br");
    var t = document.createTextNode(getTime()+ ": " + text);
    logdiv.appendChild(t);
    logdiv.appendChild(br);


// register event handlers
window.onload = function () 
    var btn1 = document.getElementById('button1');
    btn1.onclick = button1_on_click;
    
    var btn2 = document.getElementById('button2');
    btn2.onclick = button2_on_click;
;

<html><head><script src="deadlock.js"></script></head><body>
<input type="button" value="Do work1" id="button1">
<input type="button" value="Do work2" id="button2">
<div id="log"></div>
</body></html>

如果我们按下按钮 1 并在超过 1 秒后按下按钮 2 一切都会正常工作:

16:12:20: button1_on_click(): Acquiring serial port 1
16:12:20: button1 Serial port 1 acquired
16:12:21: button1: Acquiring serial port 2
16:12:21: button1 Serial port 2 acquired
16:12:21: button2_on_click(): Acquiring serial port 2
16:12:23: button1 Releasing serial port 2
16:12:23: button2 Serial port 2 acquired
16:12:23: button1 Releasing serial port 1
16:12:24: button2: Acquiring serial port 1
16:12:24: button2 Serial port 1 acquired
16:12:26: button2 Releasing serial port 1
16:12:26: button2 Releasing serial port 2

但是如果我们按下 button1 然后 button2 快速应用程序将死锁并且不再响应 button1 和 button2 的点击

16:14:28: button1_on_click(): Acquiring serial port 1
16:14:28: button1 Serial port 1 acquired
16:14:28: button2_on_click(): Acquiring serial port 2
16:14:28: button2 Serial port 2 acquired
16:14:29: button1: Acquiring serial port 2
16:14:29: button2: Acquiring serial port 1
16:14:41: button1_on_click(): Acquiring serial port 1
16:14:41: button2_on_click(): Acquiring serial port 2
16:14:41: button1_on_click(): Acquiring serial port 1
16:14:42: button2_on_click(): Acquiring serial port 2
16:14:42: button1_on_click(): Acquiring serial port 1
16:14:42: button2_on_click(): Acquiring serial port 2
16:14:45: button2_on_click(): Acquiring serial port 2
16:14:45: button2_on_click(): Acquiring serial port 2
16:14:46: button1_on_click(): Acquiring serial port 1
16:14:46: button1_on_click(): Acquiring serial port 1
16:14:47: button1_on_click(): Acquiring serial port 1

当然,我们的应用程序仍然可以处理其他事件,例如 button3。在多线程应用中情况完全一样——当thread1和thread2相互死锁时,thread3仍然可以工作。

【讨论】:

以上是关于单线程应用程序中的死锁的主要内容,如果未能解决你的问题,请参考以下文章

由 Dispatcher.Invoke 从多个线程调用的代码中的死锁

死锁Lock锁等待唤醒机制线程组线程池定时器单例设计模式_DAY24

Java线程和多线程——死锁

多线程死锁

Python程序中的线程操作-锁

OpenMP为啥这段程序是死锁?