c#中的定时器初始化和竞争条件?

时间:2013-06-23 17:23:02

标签: c# multithreading .net-4.0 timer

我在里希特的书上看到了这段代码:

  

以下代码演示了如何进行线程池线程调用   一个方法立即开始,然后每2秒开始一次:

/*1*/    internal static class TimerDemo
/*2*/    {
/*3*/        private static Timer s_timer;
/*4*/        public static void Main()
/*5*/        {
/*6*/            Console.WriteLine("Checking status every 2 seconds");
/*7*/            // Create the Timer ensuring that it never fires. This ensures that
/*8*/            // s_timer refers to it BEFORE Status is invoked by a thread pool thread
/*9*/            s_timer = new Timer(Status, null, Timeout.Infinite, Timeout.Infinite);
/*10*/            // Now that s_timer is assigned to, we can let the timer fire knowing
/*11*/            // that calling Change in Status will not throw a NullReferenceException
/*12*/            s_timer.Change(0, Timeout.Infinite);
/*13*/            Console.ReadLine(); // Prevent the process from terminating
/*14*/        }
/*15*/        // This method's signature must match the TimerCallback delegate
/*16*/        private static void Status(Object state)
/*17*/        {
/*18*/            // This method is executed by a thread pool thread
/*20*/            Console.WriteLine("In Status at {0}", DateTime.Now);
/*21*/            Thread.Sleep(1000); // Simulates other work (1 second)
/*22*/            // Just before returning, have the Timer fire again in 2 seconds
/*23*/            s_timer.Change(2000, Timeout.Infinite);
/*24*/            // When this method returns, the thread goes back
/*25*/            // to the pool and waits for another work item
/*26*/        }
/*27*/    }

然而,(抱歉),我仍然不明白行#7,#8的含义是什么

当然 - 为什么它被初始化(第9行)到Timeout.Infinite(这显然是:“不启动计时器”)

(我确实理解防止重叠的一般目的,但我相信这里还有 GC 种族条件pov。)

修改

命名空间为System.Threading

2 个答案:

答案 0 :(得分:11)

我认为这与GC没有关系,而是为了避免竞争条件

赋值操作不是原子操作:首先创建Timer对象然后分配它。

所以这是一个场景:

  • new Timer(...)创建计时器并开始“计数”

  • 当前主题在分配结束前被抢占 => s_timer仍然为空

  • 计时器唤醒另一个线程并调用Status,但初始线程尚未完成分配操作

  • Status访问s_timer,这是空引用 =>的 BOOM!

用他的方法不可能发生,例如使用相同的方案:

  • 计时器已创建,但未启动

  • 当前主题被抢先

  • 没有任何反应因为计时器尚未开始举起活动

  • 初始线程再次运行

  • 结束作业 => s_timer 引用计时器

  • 计时器安全启动:以后对Status的任何通话都有效,因为s_timer是有效参考

答案 1 :(得分:3)

这是一场比赛,但除了眼睛之外还有更多的东西。显而易见的故障模式是主线程丢失处理器并且运行一段时间,超过一秒钟。因此,永远不会在回调中更新s_timer变量kaboom。

在具有多个处理器核心的计算机上存在更微妙的问题。因为更新的变量值实际上需要在运行回调代码的cpu核心上可见。它通过缓存读取内存,该缓存容易包含陈旧内容,并且在读取时仍然将s_time变量设置为null。这通常需要内存屏障。 Thread.MemoryBarrier()方法提供了它的低级版本。发布的版本中没有任何代码可以确保发生这种情况。

它在实践中有效,因为内存屏障是隐式的。操作系统无法启动线程池线程,此处需要获取回调,而不会自行占用内存障碍。其副作用现在还确保回调线程使用s_time变量的更新值。依靠这种副作用不会赢得任何奖品,但在实践中有效。但如果不使用Richter的解决方法也不会起作用,因为在分配之前很可能会采取屏障。因此,对于具有弱内存模型的处理器,如Itanium和ARM,可能会出现故障模式。