c# – 线程安全类应该在其构造函数的末尾有内存障碍吗?

前端之家收集整理的这篇文章主要介绍了c# – 线程安全类应该在其构造函数的末尾有内存障碍吗?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
当实现一个意图成为线程安全的类时,我应该在构造函数的末尾包含一个内存屏障,以确保任何内部结构在完成之前被初始化,然后才能访问它们.或者在将实例提供给其他线程之前,消费者是否有责任插入内存障碍?

简化问题:

在下面的代码中是否存在竞争危险,可能会由于在线程安全类的初始化和访问之间缺少内存障碍而导致错误的行为?或者线程安全类本身是否可以防范?

ConcurrentQueue<int> queue = null;

Parallel.Invoke(
    () => queue = new ConcurrentQueue<int>(),() => queue?.Enqueue(5));

请注意,程序可以接受任何内容的排队,如果第二个代理在第一个执行之前执行,则会发生这种情况. (null条件运算符?保护这里的NullReferenceException.)但是,程序不应该接受IndexOutOfRangeException,NullReferenceException,多次排队5次,卡在无限循环中,或者执行其他任何操作由内部结构的种族危害引起的奇怪事件.

精心设计的问题:

具体来说,假设我正在为队列实现一个简单的线程安全的包装器. (我知道.NET已经提供了ConcurrentQueue<T>;这只是一个例子)我可以写:

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

The Java memory model doesn’t ensure that the constructor completes before the reference to the new object is assigned to instance. The Java memory model underwent a reworking for version 1.5,but double-check locking is still broken after this without a volatile variable (as in C#).

Without any memory barriers,it’s broken in the ECMA CLI specification too. It’s possible that under the .NET 2.0 memory model (which is stronger than the ECMA spec) it’s safe,but I’d rather not rely on those stronger semantics,especially if there’s any doubt as to the safety.

解决方法

懒惰< 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);
    }
});

猜你在找的C#相关文章