线程安全编程实践

在多线程程序设计中,线程安全是一个非常重要的概念。随着PLINQTask Parallel Library的出现,发现从一开始就考虑线程安全可以带来明显的性能优势。使用如ConcurrentDictionary这样的类可以大大节省时间,但有时可能会遇到需要同步但本身不是线程安全的集合。

考虑因素

当处理可能耗时的重复计算时,可能更好的选择是并行运行迭代。而对于更直接的算术运算,可能最好保持在单个线程中执行。

LazyInitializer

幸运的是,有一些现有的类可以帮助实现线程安全。例如,经常使用LazyInitializer.EnsureInitialized(ref item, ()=>{})来初始化访问器属性。但这种方法有一些严重的限制:

  • 不允许返回null。
  • 不自然地允许同步其他属性。

线程安全模式

经过深入研究,升级了使用锁和Monitor.TryEnter的最佳实践。一直都知道双重检查锁定(DCL)线程安全模式,用于检查条件,如果条件存在,则获取(或尝试)锁,然后再次检查条件再执行。但直到Lambda表达式出现,才找到了一种方法来合并实用函数来处理它。

锁定类中的每个属性

如果有一个可以被多个线程访问的类,需要对所有公共属性以及可能的一些私有属性应用线程安全。厌倦了编写:

readonly object _propA_lock = new Object();

优化线程安全的磁盘I/O(ReaderWriterLockSlim)

然后努力最大化文件读写性能。这比想象的要困难得多,并且不是一个绝对的解决方案,但它有潜力显著提高吞吐量。仍然需要捕获并重试I/O异常,其中文件被应用程序之外的访问。也在避免I/O异常时遇到了困难,如果过于积极地清理ReaderWriterLockSlim对象...

提供了一个ReadWriteHelper类,它使用ReaderWriterLockSlim对象作为基于键的锁定机制。这是优化每个锁对象重用的方法。通过允许每个锁对象持续一段时间,它允许重用,并且只有在有意义的时候才会处置它们。

使用代码

ThreadSafety暴露了一组静态方法,它们的行为类似于lock关键字,但具有一些额外的功能。包括有条件的超时锁定。

示例代码

以下是一些使用ThreadSafety类的示例代码:

object value; if (!dictionary.TryGetValue(key, out value)) { lock (dictionary) { if (!dictionary.TryGetValue(key, out value)) { dictionary.Add(key, value = newValue); } } }

简化为:

object value; ThreadSafety.LockConditional(dictionary, ()=> !dictionary.TryGetValue(key, out value), ()=> dictionary.Add(key, value = newValue));

或者更性能优化的读写版本:

object value; ThreadSafety.SynchronizeReadWrite(dictionary, key, ()=> !dictionary.TryGetValue(key, out value), ()=> dictionary.Add(key, value = newValue), 5000 /* lock-timeout */, false /* throw on error */);

注意:上述优化版本(需要一个key)已经测试过,并且与Dictionary和ConcurrentDictionary的内置GetOrAdd方法一样表现良好。

要点

将LockCleanupDelay开放给进行实验和调整。延迟清理似乎是避免文件访问冲突和担心过度锁定的解决方案。可以将其设置为零,以便在每次运行后执行清理,但在测试中,这容易引发IoException。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485