【问题标题】:Need help tracking a threaded freezing需要帮助跟踪线程冻结
【发布时间】:2011-07-17 23:18:23
【问题描述】:

我正在使用这个问题中描述的调用:Synchronization accross threads / atomic checks?

我需要创建一个任何线程都可以调用的方法调用程序,它将在主执行线程的特定给定点执行。

我最终使用了Invoker 类的这个实现: 我知道这在锁定方面可能不是最有效的,但理论上它的工作方式与Thread.MemoryBarrier() 相似,就像 SLaks 建议的那样。

编辑:根据 MRAB 的建议。

public class Invoker
{
    private Queue<Action> Actions { get; set; }

    public Invoker()
    {
        this.Actions = new Queue<Action>();
    }

    public void Execute()
    {
        Console.WriteLine("Executing {0} actions on thread {1}", this.Actions.Count, Thread.CurrentThread.ManagedThreadId);

        while (this.Actions.Count > 0)
        {
            Action action;

            lock (this.Actions)
            {
                action = this.Actions.Dequeue();
            }

            action();
        }

        Console.WriteLine("Executed, {0} actions left", this.Actions.Count);
    }

    public void Invoke(Action action, bool block = true)
    {
        if (block)
        {
            Console.WriteLine("Invoking");
            SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);

            lock (this.Actions)
            {
                this.Actions.Enqueue(delegate
                {
                    try
                    {
                        action();
                        Console.WriteLine("Actioned");
                    }
                    catch
                    {
                        Console.WriteLine("Exception thrown by action");
                        throw;
                    }
                    finally
                    {
                        semaphore.Release();
                        Console.WriteLine("Released");
                    }
                });
            }

            Console.WriteLine("Enqueued");

            Console.WriteLine("Waiting on thread {0}", Thread.CurrentThread.ManagedThreadId);
            semaphore.Wait();
            Console.WriteLine("Waited");
            semaphore.Dispose();
        }
        else
        {
            this.Actions.Enqueue(action);
        }
    }
}

许多Console.WriteLine 可以帮助我跟踪我的冻结情况,无论是否存在此日志记录都会发生这种情况(即,他们不负责冻结,可以作为罪魁祸首丢弃)。

冻结发生在以下情况:

  1. 执行线程循环运行(调用Invoker.Execute)。
  2. 在另外 2 个线程上,2 个方法相对同时被调用(调用 Invoker.Invoke)。
  3. 第一种方法可以正常工作并被正常调用,但第二种方法在“等待”之后冻结,即在 semaphore.Wait() 之后。

示例输出:

Executing 0 actions on thread 1
Executed, 0 actions left
Executing 0 actions on thread 1
Executed, 0 actions left
Invoking
Enqueued
Waiting on thread 7
Executing 1 actions on thread 1
Actioned
Released
Executed, 0 actions left
Waited
Invoking
Enqueued
Waiting on thread 8

怀疑正在发生的是执行线程以某种方式阻塞,因此没有执行第二个排队的操作,也没有释放信号量 (semaphore.Release()),因此不允许执行继续。

但这非常奇怪(在我看来),因为执行是在信号量以外的另一个线程阻塞,所以它不应该阻塞,对吧?

我尝试构建一个测试用例,在上下文环境之外重现问题,但我无法让它重现。我在这里发布它是为了说明我之前解释的 3 个步骤。

static class Earth
{
    public const bool IsRound = true;
}

class Program
{
    static Invoker Invoker = new Invoker();

    static int i;

    static void TestInvokingThread()
    {
        Invoker.Invoke(delegate { Thread.Sleep(300); }); // Simulate some work
    }

    static void TestExecutingThread()
    {
        while (Earth.IsRound)
        {
            Thread.Sleep(100); // Simulate some work

            Invoker.Execute();

            Thread.Sleep(100); // Simulate some work
        }
    }

    static void Main(string[] args)
    {
        new Thread(TestExecutingThread).Start();

        Random random = new Random();

        Thread.Sleep(random.Next(3000)); // Enter at a random point

        new Thread(TestInvokingThread).Start();
        new Thread(TestInvokingThread).Start();
    }
}

输出(应该发生):

Executing 0 actions on thread 12
Executed, 0 actions left
Executing 0 actions on thread 12
Executed, 0 actions left
Invoking
Enqueued
Waiting on thread 13
Invoking
Enqueued
Waiting on thread 14
Executing 2 actions on thread 12
Actioned
Released
Waited
Actioned
Released
Waited
Executed, 0 actions left
Executing 0 actions on thread 12
Executed, 0 actions left
Executing 0 actions on thread 12

实际问题:我现在要问的是,是否任何有经验的线程程序员都可以在Invoker 类中发现可能永远 让它阻塞,因为我认为不可能发生这种情况。同样,如果您可以说明一个使其阻塞的测试用例,我可能会找到我的错误所在。 我不知道如何隔离问题。

注意:我很确定这并不是一个真正的质量问题,因为它的特殊性,但我发帖主要是为了寻求帮助,因为这是爱好编程,我没有同事可以问。 经过一天的反复试验,我仍然无法修复它。

重要更新:我只是在第一次调用时也出现了这个错误,不一定只在第二次调用。因此,它真的可以在调用者中自行冻结。但是怎么做?在哪里?

【问题讨论】:

  • 我可以看到出于显而易见的原因进入赏金。非常感谢任何想要处理粗暴代码并且不需要赏金就没有精确问题的人。
  • 在您的 Invoke 方法中,您应该在 !block 和 Execute 方法中保护操作队列免受并发访问。你总是可以使用 ConcurrentQueue 来代替。
  • 好的,但是所有测试都是使用block = true进行的。
  • 当然。不过这很重要。
  • @Hans Passant:确实,我现在意识到了这一点。在以前的一些设计中,它是有道理的。现在就像 MRAB 的帖子一样。编辑:好像你删除了你的评论。我还编辑了我的主要帖子以反映变化。

标签: c# multithreading locking semaphore freeze


【解决方案1】:

我认为您应该在入队和出队时锁定操作。我偶尔会在这里遇到空引用异常:

this.Actions.Dequeue()();

可能是因为竞争条件。

我也认为入队代码不应该处理信号量,而只是将其留给入队线程:

        Console.WriteLine("Invoking");
        SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);

        this.Actions.Enqueue(delegate
        {
            action();
            Console.WriteLine("Actioned");
            semaphore.Release();
            Console.WriteLine("Released");
        });

        Console.WriteLine("Enqueued");

        Console.WriteLine("Waiting");
        semaphore.Wait();
        Console.WriteLine("Waited");
        semaphore.Dispose();

再次因为竞争条件。

编辑:我想到如果动作由于某种原因引发异常,信号量将不会被释放,所以:

                this.Actions.Enqueue(delegate
                {
                    try
                    {
                        action();
                        Console.WriteLine("Actioned");
                    }
                    catch
                    {
                        Console.WriteLine("Exception thrown by action");
                        throw;
                    }
                    finally
                    {
                        semaphore.Release();
                        Console.WriteLine("Released");
                    }
                });

这可能是问题吗?

编辑:你在修改 this.Actions 时是否锁定它?

出队时:

            Action action;
            lock (this.Actions)
            {
                action = this.Actions.Dequeue();
            }
            action();

并入队:

            lock (this.Actions)
            {
                this.Actions.Enqueue(delegate
                {
                    ...
                });
            }

【讨论】:

  • 感谢您的意见。这是 2 个优点。我不记得为什么我曾经想处置委托。虽然由于它没有修复任何核心错误,也没有回答主要问题,但它可以作为评论发布。
  • 所以即使进行了这些更改,它仍然会冻结?
  • 是的,确实如此。信号量可能会影响所有线程吗?我只是看不出它在逻辑上冻结在哪里。想去聊天室讨论吗? chat.stackexchange.com/rooms/836/…
  • 用您的代码更新主帖,以减少混淆。
  • @Your 编辑:很好的想法,我真的很有希望,但遗憾的是它仍然被阻止。也许在执行中可能是这样的?
【解决方案2】:

知道了。在我的程序中,这是一个非常深的多层死锁,因此我无法轻松重现它。不过,我会将 MRAB 的回答标记为已接受,因为这可能是调用程序本身导致锁定的真正原因。

【讨论】:

  • 很高兴知道您发现了问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 1970-01-01
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 2012-10-14
相关资源
最近更新 更多