c# – 线程安全类应该在其构造函数的末尾有内存障碍吗?
当实现一个意图成为线程安全的类时,我应该在构造函数的末尾包含一个内存屏障,以确保任何内部结构在完成之前被初始化,然后才能访问它们.或者在将实例提供给其他线程之前,消费者是否有责任插入内存障碍?
简化问题: 在下面的代码中是否存在竞争危险,可能会由于在线程安全类的初始化和访问之间缺少内存障碍而导致错误的行为?或者线程安全类本身是否可以防范? ConcurrentQueue<int> queue = null; Parallel.Invoke( () => queue = new ConcurrentQueue<int>(),() => queue?.Enqueue(5)); 请注意,程序可以接受任何内容的排队,如果第二个代理在第一个执行之前执行,则会发生这种情况. (null条件运算符?保护这里的NullReferenceException.)但是,程序不应该接受IndexOutOfRangeException,NullReferenceException,多次排队5次,卡在无限循环中,或者执行其他任何操作由内部结构的种族危害引起的奇怪事件. 精心设计的问题: 具体来说,假设我正在为队列实现一个简单的线程安全的包装器. (我知道.NET已经提供了 public class ThreadSafeQueue<T> { private readonly Queue<T> _queue; public ThreadSafeQueue() { _queue = new Queue<T>(); // Thread.MemoryBarrier(); // Is this line required? } public void Enqueue(T item) { lock (_queue) { _queue.Enqueue(item); } } public bool TryDequeue(out T item) { lock (_queue) { if (_queue.Count == 0) { item = default(T); return false; } item = _queue.Dequeue(); return true; } } } 一旦初始化,这个实现是线程安全的.然而,如果初始化本身被另一消费者线程所占据,则可能会出现竞争危险,由此后一线程将在内部队列T之前访问该实例.已初始化.作为一个例证: ThreadSafeQueue<int> queue = null; Parallel.For(0,10000,i => { if (i == 0) queue = new ThreadSafeQueue<int>(); else if (i % 2 == 0) queue?.Enqueue(i); else { int item = -1; if (queue?.TryDequeue(out item) == true) Console.WriteLine(item); } }); 以上代码可以错过一些数字;然而,没有内存障碍,由于内部队列< T>,可能还会得到NullReferenceException(或其他一些奇怪的结果)在调用Enqueue或TryDequeue时没有被初始化. 线程安全类的责任是在其构造函数的末尾包含一个内存障碍,还是消费者应该在类的实例化和其他线程的可见性之间包含内存障碍? .NET框架中标记为线程安全的类的约定是什么? 编辑:这是一个高级线程主题,所以我了解一些评论中的混乱.如果从其他线程访问而没有正确的同步,则实例可以显示为半烘烤.本主题在双重检查锁定的上下文中得到广泛讨论,这在ECMA CLI规范中被破坏,而不使用内存障碍(例如通过volatile).每Jon Skeet:
解决方法
懒惰< T>是线程安全初始化的一个很好的选择.我认为应该由消费者提供:
var queue = new Lazy<ThreadSafeQueue<int>>(() => new ThreadSafeQueue<int>()); Parallel.For(0,i => { else if (i % 2 == 0) queue.Value.Enqueue(i); else { int item = -1; if (queue.Value.TryDequeue(out item) == true) Console.WriteLine(item); } }); (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |