gpt4 book ai didi

c# - 线程安全类是否应该在其构造函数的末尾设置内存屏障?

转载 作者:可可西里 更新时间:2023-11-01 03:11:17 24 4
gpt4 key购买 nike

当实现一个旨在线程安全的类时,我是否应该在其构造函数的末尾包含一个内存屏障,以确保任何内部结构在它们可以被访问之前已经完成初始化?还是消费者有责任在使实例对其他线程可用之前插入内存屏障?

简化问题:

由于在线程安全类的初始化和访问之间缺少内存屏障,下面的代码中是否存在可能导致错误行为的竞争危险?或者线程安全类本身应该防止这种情况发生吗?

ConcurrentQueue<int> queue = null;

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

请注意,程序不入队是可以接受的,如果第二个委托(delegate)在第一个委托(delegate)之前执行就会发生这种情况。 (空条件运算符 ?. 在这里防止 NullReferenceException。)但是,程序抛出 IndexOutOfRangeException 是 Not Acceptable 。 , 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;
}
}
}

这个实现是线程安全的,一旦初始化。但是,如果初始化本身被另一个消费者线程竞速,则可能会出现竞态危险,后者线程将在内部 Queue<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);
}
});

上面的代码漏掉一些数字是可以接受的;然而,如果没有内存屏障,它也可能会得到一个 NullReferenceException。 (或其他一些奇怪的结果)由于内部 Queue<T>Enqueue 之前尚未初始化或 TryDequeue被调用。

线程安全类的责任是在其构造函数的末尾包含一个内存屏障,还是消费者应该在类的实例化和它对其他线程的可见性之间包含一个内存屏障?对于标记为线程安全的类,.NET Framework 中的约定是什么?

编辑:这是一个高级线程主题,所以我理解一些评论中的混淆。如果在没有适当同步的情况下从其他线程访问,一个实例可能显示为半生不熟。本主题在双重检查锁定的上下文中进行了广泛讨论,该锁定根据 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.

最佳答案

Lazy<T>是线程安全初始化的一个很好的选择。我认为应该由消费者提供:

var queue = new Lazy<ThreadSafeQueue<int>>(() => new ThreadSafeQueue<int>());

Parallel.For(0, 10000, 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# - 线程安全类是否应该在其构造函数的末尾设置内存屏障?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38881722/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com