当前位置: 首页 > 知识库问答 >
问题:

访问StackExchange时发生死锁。雷迪斯

逄征
2023-03-14

我在调用StackExchange时遇到死锁情况。雷迪斯。

我不知道到底发生了什么,这非常令人沮丧,我将感谢任何有助于解决或解决这个问题的意见。

万一你也有这个问题,不想读这些东西;我建议您尝试将preserveAncyOrder设置为false

ConnectionMultiplexer connection = ...;
connection.PreserveAsyncOrder = false;

这样做可能会解决这种死锁

  • 该代码作为控制台应用程序或Azure Worker角色运行
  • 它使用HttpMessageHandler公开RESTAPI,因此入口点是异步的
  • 代码的某些部分具有线程关联性(由单个线程拥有,并且必须由单个线程运行)
  • 代码的某些部分只是异步的
  • 我们正在做同步对异步和异步对同步反模式。(混合等待等待()/结果
  • 我们只在访问Redis时使用异步方法
  • 我们用的是StackExchange。Redis 1.0.450适用于。净额4.5

应用程序/服务启动时,它会正常运行一段时间,然后突然(几乎)所有传入的请求都停止运行,它们永远不会产生响应。所有这些请求都陷入僵局,等待对Redis的调用完成。

有趣的是,一旦死锁发生,对Redis的任何调用都将挂起,但前提是这些调用是从传入的API请求发出的,该请求在线程池上运行。

我们也在从低优先级后台线程调用Redis,即使在死锁发生后,这些调用仍在继续运行。

似乎只有在线程池线程上调用Redis时才会发生死锁。 我不再认为这是因为这些调用是在线程池线程上进行的。相反,似乎任何不带延续的异步Redis调用,或带有同步安全延续的异步Redis调用,即使在出现死锁情况后也会继续工作。(见下文我的想法)

>

由混合等待任务引起的死锁。结果(sync-over-async,就像我们做的那样)。但是我们的代码是在没有同步上下文的情况下运行的,所以这在这里不适用,对吗?

如何安全地混合使用同步和异步代码?

是的,我们不应该那样做。我们必须这样做一段时间,但我们将继续这样做。大量需要迁移到异步世界的代码。

同样,我们没有同步上下文,所以这不应该导致死锁,对吗?

在任何await之前设置ConfigureAwait(false)对此没有影响。

异步命令和任务后出现超时异常。当你在交易所等待的时候。雷迪斯

这就是线程劫持问题。目前的情况如何?这就是问题所在吗?

斯塔克交换。Redis异步调用挂起

马克的回答是:

...混合等待和等待不是个好主意。除了死锁,这是“异步同步”——一种反模式。

但他也表示:

Redis在内部绕过同步上下文(对库代码来说很正常),所以它不应该有死锁

所以,从我的理解来看。Redis应该不知道我们是否在使用异步同步反模式。不建议使用它,因为它可能会导致其他代码中的死锁。

然而,在这种情况下,据我所知,死锁实际上是在StackExchange内部。雷迪斯。如果我说错了,请纠正我。

我发现死锁似乎源于CompletionManager的第124行的ProcessAsyncCompletionQueue。cs

代码片段:

while (Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) != 0)
{
    // if we don't win the lock, check whether there is still work; if there is we
    // need to retry to prevent a nasty race condition
    lock(asyncCompletionQueue)
    {
        if (asyncCompletionQueue.Count == 0) return; // another thread drained it; can exit
    }
    Thread.Sleep(1);
}

我发现在僵局期间activeAsyncWorkerThread是我们等待Redis调用完成的线程之一。(我们的线程=运行代码的线程池线程)。因此,上述循环被视为永远持续。

不知道细节,这肯定感觉不对;斯塔克交换。Redis正在等待一个它认为是活动异步工作线程的线程,而实际上它是一个与此相反的线程。

不知道这是不是因为线程劫持问题(我也不完全理解)?

我想弄清楚的主要两个问题是:

>

  • 即使在没有同步上下文的情况下运行,混合wait等待()/Result会导致死锁吗?

    我们是否在StackExchange中遇到错误/限制。雷迪斯?

    从我的调试结果来看,问题似乎是:

    next.TryComplete(true);
    

    ...在CompletionManager.cs的第162行上,在某些情况下可能会让当前线程(即活动异步工作线程)游走并开始处理其他代码,可能会导致死锁。

    在不知道细节和只考虑这个“事实”的情况下,在TryComplete调用期间临时释放活动的异步工作线程似乎是合乎逻辑的。

    我想这样的事情可能会奏效:

    // release the "active thread lock" while invoking the completion action
    Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread);
    
    try
    {
        next.TryComplete(true);
        Interlocked.Increment(ref completedAsync);
    }
    finally
    {
        // try to re-take the "active thread lock" again
        if (Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) != 0)
        {
            break; // someone else took over
        }
    }
    

    我想我最好的希望是马克·格拉维尔会读到这篇文章并提供一些反馈:-)

    我在上面写过,我们的代码不使用同步上下文。这只是部分正确:代码作为控制台应用程序或Azure Worker角色运行。在这些环境中,SynchronizationContext。当前的null,这就是为什么我写到我们在没有同步上下文的情况下运行。

    然而,在阅读了SynchronizationContext之后,我了解到事实并非如此:

    按照约定,如果线程的当前SynchronizationContext为null,则它隐式具有默认SynchronizationContext。

    不过,默认同步上下文不应该是死锁的原因,因为基于UI(WinForms,WPF)的同步上下文可能会导致死锁,因为它并不意味着线程关联。

    消息完成后,将检查其完成源是否被视为同步安全。如果是,则完成操作以内联方式执行,一切正常。

    如果不是,想法是在新分配的线程池线程上执行完成操作。当ConnectionMultiplexer时,这也可以正常工作。为false。

    但是,当连接多路复用器时。PreserveAncyOrdertrue(默认值),则这些线程池线程将使用完成队列序列化其工作,并确保在任何时候,其中最多有一个线程是活动的异步工作线程。

    当一个线程成为活动的异步工作线程时,它将继续如此,直到它耗尽完成队列。

    问题是,完成操作不是同步安全的(从上面看),但它仍然是在一个线程上执行的,该线程不能被阻止,因为这将阻止其他非同步安全消息的完成。

    请注意,使用同步安全的完成操作完成的其他消息将继续正常工作,即使活动异步工作线程被阻塞。

    我建议的“修复”(上面)不会以这种方式造成僵局,但是它会破坏保留异步完成顺序的概念。

    因此,也许这里要得出的结论是,将waitResult/等等()混合在一起是不安全的,当PrelveAsyncOrdertrue时,无论我们是否在没有同步的情况下运行上下文?

    (至少在我们能够使用.NET 4.6和新的任务创建选项.runcontinuations同步之前,我想)


  • 共有2个答案

    钱卓君
    2023-03-14

    基于上面的详细信息,我猜了很多,但不知道您拥有的源代码。听起来,您可能遇到了一些内部的、可配置的限制。网您不应该点击这些,所以我猜您没有处理对象,因为它们在线程之间浮动,这不允许您使用using语句来干净地处理它们的对象生存期。

    这详细说明了HTTP请求的限制。与旧的WCF问题类似,当您没有处理连接时,所有WCF连接都将失败。

    最大并发HttpWeb请求数

    这更像是一种调试帮助,因为我怀疑您是否真的在使用所有TCP端口,但是关于如何找到有多少打开的端口以及到哪里的信息很好。

    https://msdn.microsoft.com/en-us/library/aa560610(v=bts.20)。aspx

    都才俊
    2023-03-14

    以下是我发现的解决此死锁问题的变通方法:

    默认情况下StackExchange。Redis将确保命令以与接收结果消息相同的顺序完成。这可能会导致如本问题所述的死锁。

    通过将PrelveAsyncOrder设置为false来禁用该行为。

    ConnectionMultiplexer connection = ...;
    connection.PreserveAsyncOrder = false;
    

    这将避免死锁,还可以提高性能。

    我鼓励任何遇到死锁问题的人尝试这种解决方法,因为它是如此干净和简单。

    您将无法保证异步continuations的调用顺序与底层Redis操作的完成顺序相同。然而,我真的不明白为什么这是你会依赖的东西。

    StackExchange中的活动异步工作线程发生死锁。Redis在内联执行完成任务时完成命令。

    可以通过使用自定义的TaskScheduler防止内联执行任务,并确保TryExecuteTaskInline返回false

    public class MyScheduler : TaskScheduler
    {
        public override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            return false; // Never allow inlining.
        }
    
        // TODO: Rest of TaskScheduler implementation goes here...
    }
    

    实现一个好的任务调度器可能是一项复杂的任务。但是,ParallelExtensionExtras库(Nuget包)中存在一些可以使用或从中获得灵感的现有实现。

    如果您的任务调度器将使用自己的线程(而不是来自线程池),那么最好允许内联,除非当前线程来自线程池。这将起作用,因为StackExchange中的活动异步工作线程。Redis始终是一个线程池线程

    public override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        // Don't allow inlining on a thread pool thread.
        return !Thread.CurrentThread.IsThreadPoolThread && this.TryExecuteTask(task);
    }
    

    另一个想法是使用线程本地存储将调度程序连接到其所有线程。

    private static ThreadLocal<TaskScheduler> __attachedScheduler 
                       = new ThreadLocal<TaskScheduler>();
    

    确保在线程开始运行时分配此字段,并在线程完成时清除此字段:

    private void ThreadProc()
    {
        // Attach scheduler to thread
        __attachedScheduler.Value = this;
    
        try
        {
            // TODO: Actual thread proc goes here...
        }
        finally
        {
            // Detach scheduler from thread
            __attachedScheduler.Value = null;
        }
    }
    

    然后,您可以允许任务内联,只要任务是在自定义计划程序“拥有”的线程上完成的:

    public override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        // Allow inlining on our own threads.
        return __attachedScheduler.Value == this && this.TryExecuteTask(task);
    }
    
     类似资料:
    • 死锁描述了另外两个线程因为永远等待对方而被阻塞的情况。当死锁发生时,程序永远挂起,你唯一能做的就是杀死程序。 为什么在下面给出的示例生产者-消费者问题中没有发生死锁: 我想知道为什么当同步对象正在等待其他线程释放锁时,在同步块中调用等待方法不会导致死锁?

    • 表以作为主键,列(CHAR(1))可以是“x”或“y”,以及(INT)。这些列上没有索引。存储引擎是InnoDB。 state_positions必须总是连续的,并且对于某个状态可能永远不会有重复的位置,即如果我有5个状态为“x”的用户,他们的state_positions必须是1、2、3、4、5 这是我正在运行的导致死锁的查询: 为了测试,我同时插入了大量的用户,但每次都出现死锁错误。 我读了这

    • 问题内容: 谁能解释一下为什么这段代码中会出现死锁。 问题答案: 这可能是如何执行的。 输入,由于关键字已锁定Alphonse 输入,加斯顿现已锁定 无法通过第一个方法调用执行,因为加斯顿(鲍尔)被锁定。等待锁被释放。 由于alphonse(上弦器)已锁定,因此无法从第二个方法调用执行。等待锁被释放。 两个线程都互相等待释放锁。

    • 主要内容:示例,死锁解决方案示例死锁描述了两个或多个线程等待彼此而被永久阻塞的情况。 当多个线程需要相同的锁定但以不同的顺序获取时,会发生死锁。 Java多线程程序可能会遇到死锁状况,因为关键字会导致执行线程在等待与指定对象相关联的锁定或监视时出现阻止情况。 看看下面一个例子。 示例 当您编译并执行上述程序时,会出现死锁情况,以下是程序生成的输出 - 上述程序将永久挂起,因为两个线程都不能继续进行,等待彼此释放锁定,所以您可以按

    • 问题内容: 我在项目中使用了hibernate模式,并且由于非常简单的数据库操作而获得了随机的表观死锁。 有一个堆栈跟踪:https : //gist.github.com/knyttl/8999006 –让我感到困惑的是,第一个异常是RollbackException,然后是LockAquisition异常。 问题经常发生在类似的条款上: 我很困惑,因为我不知道这是Hibernate,MySQL

    • 我发现了一个SQL死锁问题,当函数由两个用户并发执行时会发生该问题。我有一个PHP函数,它执行几个包含在事务中的数据库插入查询。其中一个插件也触发了一个触发器。请参阅下面的我的表模式和代码示例。 主表 历史表格 审计表 我在main_table上有一个触发器,定义如下。它所做的是从audit_table中选择最大id并将记录插入到history_table。 下面是由两个用户同时执行的函数。插入记