我们说我有一个计时器(例如System.Timers.Timer),我们知道每个elasped事件都会被放入线程池中。如果事件足够快,则线程池如何管理对共享变量的访问(例如全局int计数器)。经理是否在引擎盖下使用信号量/锁?
或者它没有做任何事情,只是简单地在线程池的开头复制共享变量,最后一个完成的线程将设置正确的变量值?
不幸的是,我无法对此进行测试,因为在每个已用事件之间无法保证触发事件的顺序(例如,使用计数器变量不可靠),因为它们可能无序触发。
由于
答案 0 :(得分:2)
您必须自己管理对共享变量的多线程访问。
StackOverflow和Google上有很多答案解释如何执行此操作,搜索"线程安全C#"。
我曾经处理过很多潜在线程问题的大型项目,而我编写的代码才有效。我现在很擅长编写线程安全代码,因为我已经犯了所有可能的错误。
如果您只是学习编写线程安全代码,那么它很容易被大量的信息所淹没。您可能会找到cover the 8 different types of synchronization primitives的某些页面。你会发现有关这个主题的大量讨论,其中只有一半会有所帮助。
如果您是第一次关注学习曲线,我建议您暂时忽略所述噪音,而是首先专注于掌握这两条规则:
如果任何两个线程写入某个共享原语(如long
或Dictionary
或List
),请在此共享原语的访问周围添加lock
。针对某种情况,以便在完成锁定后,数据结构将完全更新。这是编写线程安全代码的核心:所有其他线程规则都可以从这个代码中派生出来。
示例:强>
// This _lock should be initialized once on program startup, and should be global.
static readonly object _dictLock = new object();
// This data structure can be accessed by multiple threads.
public static Dictionary<string, int> dict = new Dictionary<string, int>();
lock (_dictLock)
{
if (dict.ContainsKey("Hello") == false)
{
dict.Add("Hello", 42);
}
} // Lock exits: data structure is now completely 100% updated. Google "atomic access C#".
尽量不要锁定锁。如果以错误的顺序输入锁,则可能会产生死锁。如果您只锁定基元(例如dictionary
,long
,string
等),那么这不应该是一个问题。
如果您只是学习,只使用lock
,请参阅how to use lock。如果只是这个就很难出错,因为当函数退出时会自动释放锁。您可以在以后升级到其他类型的锁,例如读写锁。不要为ConcurrentDictionary
或Interlocked.Increment
而烦恼 - 专注于让基本知识正确无误。
尝试尽可能少花时间锁。不要锁定大量代码,将锁定放在代码中最小的部分,通常是dictionary
或long
。除非有争议,否则锁定速度非常快,因此这种技术似乎可以很好地创建快速的线程安全代码。
根据我的经验,线程不安全代码的最大原因是Dictionary
。即使ConcurrentDictionary
也不能幸免 - 如果访问分布在多条线路上,它需要手动锁定才能正确。如果你做对了,你将消除代码中95%的有意义的线程问题。
答案 1 :(得分:0)
线程池不能神奇地使您的共享可变变量成为线程安全的。它无法控制它们,甚至不知道它们存在。
请注意,计时器滴答可以同时发生(即使在低频率下),也可能在定时器处理完毕后发生。您需要执行任何必要的同步。
线程池本身是线程安全的,因为我可以成功处理并发工作项(这是重点)。