2011-07-17 69 views
1

我用了这个问题描述的调用: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个方法(调用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 

实际问题:我在问什么,在这一点上,是,如果有经验的线程的程序员可以看到一个合乎逻辑的错误类可能有史以来使它阻止,因为我看不到发生这种情况的可能方式。同样,如果你能说明一个阻止它的测试用例,我可能会发现我的错在哪里。 我不知道如何隔离问题。

注意:我敢肯定这是不是真的认为是其特异性的质量问题,但我大多张贴作为帮助一个绝望的哭泣,因为这是爱好编程,我没有同事问。 经过一天的试验和错误,我仍然无法修复它。

重要更新:我刚刚在第一次调用时发生了这个错误,不一定只在第二次调用时发生。因此,它可以真正冻结在调用者本身。但是如何?哪里?

+0

我可以看到,为了明显的原因进入赏金。非常感谢任何人想要解决粗暴的代码,没有精确的问题而不需要奖励。 – Lazlo

+0

在您的Invoke方法中,当!block和Execute方法中类似时,应该保护Actions Queue免受并发访问的影响。您可以始终使用ConcurrentQueue。 – spender

+0

好的,但所有的测试都是通过'block = true'进行的。 – Lazlo

回答

2

我想你应该在排队和出队时锁定Actions。我偶尔在这里有一个空引用异常:

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

感谢您的输入。这是2点好处。我不记得我为什么想要在代表中处置。虽然它不能解决任何核心错误,也不能回答主要问题,但它可以作为评论发布。 – Lazlo

+0

那么即使有这些变化它仍然冻结? – MRAB

+0

是的,它的确如此。信号量可能影响所有线程?我只是没有看到它在逻辑上冻结的地方。感觉像去聊天讨论? http://chat.stackexchange.com/rooms/836/need-help-tracking-a-threaded-freezing – Lazlo

0

明白了。这是我的计划中一个非常深的多层僵局,因此我无法轻易再现它。但是,我会将MRAB的答案标记为已接受,因为它可能是Invoker本身造成锁定的真正原因。

+0

很高兴知道你发现了这个问题。 – MRAB