需要帮助跟踪螺纹冻结

时间:2011-07-17 23:18:23

标签: c# multithreading locking semaphore freeze

我正在使用此问题中描述的调用: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()之后冻结。
  4. 示例输出:

    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类中看到曾经< / em>让它阻止,因为我看不到发生这种情况的可能方式。同样,如果你能说明一个阻止它的测试用例,我可能会发现我的错误。 我不知道如何隔离问题。

    注意:我很确定这并不是因为它的特殊性而被视为一个质量问题,但我主要是张贴作为绝望的呼救,因为这是业余爱好编程和我没有同事问。 经过一天的反复试验,我仍然无法修复它。

    重要更新:我刚刚在第一次调用时遇到此错误,不一定只在第二次调用时发生。因此,它可以在调用者中自行冻结。但是怎么样?在哪里?

2 个答案:

答案 0 :(得分:2)

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

this.Actions.Dequeue()();

可能是因为竞争条件。

我也认为排队的代码不应该处理semphore,而只是把它留给排队的线程:

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

答案 1 :(得分:0)

知道了。在我的程序中,这是一个非常深刻的多层死锁,因此我无法轻易地重现它。我会将MRAB的答案标记为已被接受,因为它可能是由Invoker本身引起的锁定的真正原因。