需要帮助跟踪螺纹冻结

本文关键字:冻结 跟踪 帮助 | 更新日期: 2023-09-27 18:02:16

我使用的是本问题中描述的调用:跨线程/原子检查的同步?

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

我最终使用了Invoker类的这个实现:我知道,就锁定而言,is可能不是最有效的,但理论上它的工作方式与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. 在另外两个线程上,相对同时调用两个方法(调用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类中看到一个逻辑错误,该错误可能会使其阻塞,因为我认为这是不可能发生的。同样,如果你能举例说明一个使它阻塞的测试用例,我可能会发现我的测试用例哪里出了问题<我不知道如何孤立这个问题>

注意:我很确定这并不是一个真正的质量问题,因为它的特殊性,但我发布的帖子大多是绝望的呼救,因为这是业余编程,我没有同事可以问经过一天的尝试和错误,我仍然无法修复它。

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

需要帮助跟踪螺纹冻结

我认为在排队和出队时应该锁定Actions。我偶尔会在这里遇到一个空引用异常:

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
                {
                    ...
                });
            }

明白了。在我的程序中,这是一个非常严重的多层死锁,因此我无法轻松复制它。然而,我将把MRAB的答案标记为已接受,因为它可能是Invoker本身导致锁定的真正原因。