.NET中共享变量的线程池管理

时间:2015-07-01 06:54:57

标签: c# .net multithreading threadpool

我们说我有一个计时器(例如System.Timers.Timer),我们知道每个elasped事件都会被放入线程池中。如果事件足够快,则线程池如何管理对共享变量的访问(例如全局int计数器)。经理是否在引擎盖下使用信号量/锁?

或者它没有做任何事情,只是简单地在线程池的开头复制共享变量,最后一个完成的线程将设置正确的变量值?

不幸的是,我无法对此进行测试,因为在每个已用事件之间无法保证触发事件的顺序(例如,使用计数器变量不可靠),因为它们可能无序触发。

由于

2 个答案:

答案 0 :(得分:2)

您必须自己管理对共享变量的多线程访问。

StackOverflow和Google上有很多答案解释如何执行此操作,搜索"线程安全C#"。

我曾经处理过很多潜在线程问题的大型项目,而我编写的代码才有效。我现在很擅长编写线程安全代码,因为我已经犯了所有可能的错误。

如果您只是学习编写线程安全代码,那么它很容易被大量的信息所淹没。您可能会找到cover the 8 different types of synchronization primitives的某些页面。你会发现有关这个主题的大量讨论,其中只有一半会有所帮助。

如果您是第一次关注学习曲线,我建议您暂时忽略所述噪音,而是首先专注于掌握这两条规则:

规则1

如果任何两个线程写入某个共享原语(如longDictionaryList),请在此共享原语的访问周围添加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#".

规则2

尽量不要锁定锁。如果以错误的顺序输入锁,则可能会产生死锁。如果您只锁定基元(例如dictionarylongstring等),那么这不应该是一个问题。

准则1

如果您只是学习,只使用lock,请参阅how to use lock。如果只是这个就很难出错,因为当函数退出时会自动释放锁。您可以在以后升级到其他类型的锁,例如读写锁。不要为ConcurrentDictionaryInterlocked.Increment而烦恼 - 专注于让基本知识正确无误。

准则2

尝试尽可能少花时间锁。不要锁定大量代码,将锁定放在代码中最小的部分,通常是dictionarylong。除非有争议,否则锁定速度非常快,因此这种技术似乎可以很好地创建快速的线程安全代码。

95%有意义的线程问题的原因?

根据我的经验,线程不安全代码的最大原因是Dictionary。即使ConcurrentDictionary也不能幸免 - 如果访问分布在多条线路上,它需要手动锁定才能正确。如果你做对了,你将消除代码中95%的有意义的线程问题。

答案 1 :(得分:0)

线程池不能神奇地使您的共享可变变量成为线程安全的。它无法控制它们,甚至不知道它们存在。

请注意,计时器滴答可以同时发生(即使在低频率下),也可能在定时器处理完毕后发生。您需要执行任何必要的同步。

线程池本身是线程安全的,因为我可以成功处理并发工作项(这是重点)。