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

我在Richter的书上看到了这段代码:

下面的代码演示了如何让线程池线程调用立即启动的方法,然后每隔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竞争条件。)

编辑

命名空间是System.Threading

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

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

所以这是一个场景:

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

  • 赋值结束之前 ,当前线程被抢占 => s_timer仍然为空

  • 计时器在另一个线程上唤醒并调用Status但初始线程尚未完成赋值操作

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

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

  • 计时器已创建但未启动

  • 当前线程被抢占

  • 没有任何反应,因为计时器还没有开始提升事件

  • 初始线程再次运行

  • 结束赋值 => s_timer 引用计时器

  • 计时器安全启动:以后对Status任何调用都是有效的,因为s_timer是一个有效的引用

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

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

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