C# 当我只读取而不更改队列时,我应该使用锁定语句吗?

Posted

技术标签:

【中文标题】C# 当我只读取而不更改队列时,我应该使用锁定语句吗?【英文标题】:C# Should I use lock statement when I am only reading and not changing queue? 【发布时间】:2020-09-15 17:34:32 【问题描述】:

我在我的多线程 C# 应用程序中使用列表作为队列。 我正在锁定我的自定义 Enqueue 和 Dequeue 方法。因为多个线程可以调用这些方法。

发件人:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement 我想我应该,但我仍然会在这里问这个问题。 我应该锁定只告诉我哪个元素处于零索引的 Peek 方法吗? 出于示例的目的,请考虑我有整数列表(队列)。

list = [1, 2, 2, 5]
Enqueue(list, 6)
// [1, 2, 2, 5, 6]

var element = Dequeue(list) // element = 1;
// [2, 2, 5, 6]

var peekElement = Peek(list) // peekElement = 2
// [2, 2, 5, 6]

【问题讨论】:

【参考方案1】:

为什么不使用可以轻松完成工作的 ConcurrentQueue?

var queue = new ConcurrentQueue<int>();

queue.Enqueue(1);
queue.Enqueue(2);

if (queue.TryPeek(out int firstValue))
    Console.WriteLine("Peek: " + firstValue);

if (queue.TryDequeue(out int result))
    Console.WriteLine("Dequeue: " + result);

https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1?view=netcore-3.1

【讨论】:

很好的解决方案,不知道 msdn 中的那种结构。我将假设使用 ConcurrentQueue。【参考方案2】:

你应该锁定。

我想你的 Peek() 方法看起来像这样:

public int Peek(List<int> list)

    if (list.Length < 1)
        throw new InvalidOperationException("Queue is empty");

    return list[0];

如果您尝试查看空队列,您希望它会抛出带有消息“队列为空”的 InvalidOperationException

但是现在想象一下这个场景有两个线程,A 和 B:

    线程 A 调用 Peek() 以获取具有一个元素的列表。 线程A执行if (list.Length &lt; 1),判断列表不为空,所以不会抛出异常。 线程 B 调用 Dequeue() 并清空列表。 线程 A 移动到 return list[0] 并且哎呀 - 列表是空的,所以你会得到一个完全不同的 ArgumentOutOfRangeException 异常。

【讨论】:

【参考方案3】:

我应该锁定 Peek 方法吗...?

假设您不接受 Abhay 的建议(请参阅其他答案),那么是的。

锁定锁的真正目的是确保刚刚锁定锁的线程将看到共享变量的状态与其他更改的线程留下的状态一致在它释放同一个锁之前。

您示例中的list 变量具有一些内部表示,可能比简单的数组更复杂。如果在其他线程 W 可以随时修改列表时允许某个线程 R 查看list 而不锁定锁,那么您不仅会冒险让线程 R 看到列表的某些过时版本;您还会冒着线程 R 看到列表的损坏版本的风险。

即使像询问列表长度这样简单的事情也可能导致线程 R 跟随错误的指针并使程序崩溃,或者更糟。

【讨论】:

【参考方案4】:

不,你不应该。

您应该使用不是语句的修改锁 - MUltipleReaderSingleWriter。

它允许多次读取,但只能写入一次,并且只有在没有读取器处于活动状态时才能写入。

【讨论】:

【参考方案5】:

来自Queue&lt;T&gt; 类的documentation:

这种类型的公共静态成员是线程安全的。不保证任何实例成员都是线程安全的。

只要不修改集合,Queue&lt;T&gt; 可以同时支持多个读取器。即便如此,通过集合枚举本质上不是线程安全的过程。有关线程安全队列,请参阅ConcurrentQueue&lt;T&gt;

因此,如果您从一个线程调用Peek,而队列被其他线程同时修改,而没有使用lock 或其他方式正确同步对队列的访问,您将违反制造商提供的保证班上。类的行为正式成为“未定义”。如果您正在尝试制作一个关于其结果的正确性应该可靠的程序,这不是一个好主意。

【讨论】:

以上是关于C# 当我只读取而不更改队列时,我应该使用锁定语句吗?的主要内容,如果未能解决你的问题,请参考以下文章

C#中的锁定

使用 BinaryReader/Writer 的 C# Socket 从读取器获取错误数据

Berkeley DB:锁定记录而不读取它

在中等信任的 ASP.Net 中读取程序集 Guid 而不锁定 appdomain 中的 DLL

我可以在 C++ 中读取 Windows 中的文件而不锁定包含该文件的文件夹吗

仅在复制/粘贴时锁定文件,而不是在剪切/粘贴时锁定