Timer initialization and Race condition in c#?

一笑奈何 提交于 2019-12-05 06:40:45

I think it's not related to the GC but rather to avoid a race condition:

The assignment operation is not atomic: first you create the Timer object then you assign it.

So here is a scenario:

  • new Timer(...) creates the timer and it starts "counting"

  • the current thread is preempted BEFORE the assignment ends => s_timer is still null

  • the timer wakes up on another thread and calls Status but the initial thread has not yet finished the assignment operation!

  • Status accesses s_timer which is a null reference => BOOM!

With his method it can't happen, e.g. with the same scenario:

  • the timer is created but does not start

  • the current thread is preempted

  • nothing happens because the timer has not yet started to raise events

  • the initial thread is running again

  • it ends the assignment => s_timer references the timer

  • the timer is started safely: any future call to Status is valid because s_timer is a valid reference

It is a race, but there's more to it than meets the eye. The obvious failure mode is when the main thread loses the processor and doesn't run for a while, more than a second. And thus never gets around to updating the s_timer variable, kaboom in the callback.

A much more subtle issue is present on machines with multiple processor cores. In that the updated variable value actually needs to be visible on the cpu core that runs the callback code. Which reads memory through a cache, that cache is liable to contain stale content and still have the s_time variable at null when it is read. That normally requires a memory barrier. A low-level version of it is available from the Thread.MemoryBarrier() method. There is no code whatsoever in the posted version that ensures that this happens.

It works in practice because the memory barrier is implicit. The operating system cannot get a threadpool thread started, required here to get the callback going, without itself taking a memory barrier. The side effect of which now also also ensures that the callback thread uses the update value of the s_time variable. Relying on this side-effect doesn't win any prizes, but works in practice. But also won't work if Richter's workaround isn't used since the barrier may well be taken before the assignment. And thus the likelier failure mode on processors with a weak memory model, like Itanium and ARM.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!