简体   繁体   English

C# 只读不改队列是否应该使用lock语句?

[英]C# Should I use lock statement when I am only reading and not changing queue?

I am using list as a queue in my multithreading C# application.我在我的多线程 C# 应用程序中使用列表作为队列。 I am locking my custom Enqueue and Dequeue methods.我正在锁定我的自定义入队和出队方法。 Because multiple threads can call those methods.因为多个线程可以调用那些方法。

From: https://learn.microsoft.com/en-us/do.net/csharp/language-reference/keywords/lock-statement I think I should but I would still ask the question here.来自: https://learn.microsoft.com/en-us/do.net/csharp/language-reference/keywords/lock-statement我想我应该,但我仍然会在这里问这个问题。 Should I lock Peek method that is only telling me which element is at zero index?我应该锁定只告诉我哪个元素索引为零的 Peek 方法吗? For the purpose of example consider that I have list(queue) of integers.出于示例的目的,考虑我有整数列表(队列)。

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]

Why not use ConcurrentQueue which will do your job easily?为什么不使用可以轻松完成工作的 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://learn.microsoft.com/en-us/do.net/api/system.collections.concurrent.concurrentqueue-1?view.netcore-3.1 https://learn.microsoft.com/en-us/do.net/api/system.collections.concurrent.concurrentqueue-1?view.netcore-3.1

You should lock.你应该锁定。

I imagine that your Peek() method looks something like this:我想您的Peek()方法看起来像这样:

public int Peek(List<int> list)
{
    if (list.Length < 1)
        throw new InvalidOperationException("Queue is empty");

    return list[0];
}

You are expecting it to throw an InvalidOperationException with message "Queue is empty" if you try to peek into an empty queue.如果您尝试查看空队列,您预计它会抛出带有消息“队列为空”的InvalidOperationException

But now imagine this scenario with two threads, A and B:但是现在想象一下有两个线程 A 和 B 的场景:

  1. Thread A calls Peek() for a list with one element.线程 A 为包含一个元素的列表调用 Peek()。
  2. Thread A executes if (list.Length < 1) and determines that the list is not empty, so it won't throw an exception.线程A执行if (list.Length < 1) ,判断list不为空,所以不会抛出异常。
  3. Thread B calls Dequeue() and empties the list.线程 B 调用 Dequeue() 并清空列表。
  4. Thread A moves on to return list[0] and whoops - the list is empty so you get a completely different ArgumentOutOfRangeException exception.线程 A 继续return list[0]并且糟糕 - 该列表为空,因此您会得到一个完全不同的ArgumentOutOfRangeException异常。

Should I lock Peek method...?我应该锁定 Peek 方法......?

Assuming you don't take Abhay's advice (see other answer) then yes.假设您不接受 Abhay 的建议(请参阅其他答案),那么可以。

The real purpose of locking a lock is to ensure that the thread which has just locked the lock will see shared variables in a state that is consistent with how they were left by some other thread that changed them before it released the same lock.锁定锁的真正目的是确保刚刚锁定锁的线程将看到state 中的共享变量,这与释放相同锁之前更改它们的其他线程留下的方式一致。

The list variable in your example has some internal representation that may be more complicated than just a simple array.您示例中的list变量具有一些内部表示,可能比简单数组更复杂。 If you allow some thread R to look at the list without locking a lock when some other thread W could modify the list at any time, you don't merely risk thread R seeing some out-of-date version of the list;如果您允许某些线程 R 在不锁定锁的情况下查看list ,而其他线程 W 可以随时修改列表,您不仅会冒线程 R 看到列表的某些过时版本的风险; you also risk thread R seeing a corrupt version of the list.您还冒着线程 R 看到列表的损坏版本的风险。

Even something as simple as asking for the length of the list could cause thread R to follow a bad pointer and crash the program, or worse.即使像询问列表长度这样简单的事情也可能导致线程 R 跟随一个错误的指针并使程序崩溃,或者更糟。

No, you should not.不,你不应该。

You should use a modified lock which is not a statement - a MUltipleReaderSingleWriter.您应该使用不是语句的修改锁 - MUltipleReaderSingleWriter。

It allows multiple reads, but only ONE write and that only when there is no reader active.它允许多次读取,但只允许一次写入,并且只有在没有读取器处于活动状态时才允许写入。

From the documentation of the Queue<T> class:来自Queue<T> class 的文档

Public static members of this type are thread safe. public static 这种类型的成员是线程安全的。 Any instance members are not guaranteed to be thread safe.不保证任何实例成员都是线程安全的。

A Queue<T> can support multiple readers concurrently, as long as the collection is not modified.只要集合未被修改, Queue<T>就可以同时支持多个读取器。 Even so, enumerating through a collection is intrinsically not a thread-safe procedure.即便如此,通过集合进行枚举本质上不是线程安全的过程。 For a thread-safe queue, see ConcurrentQueue<T> .有关线程安全队列,请参阅ConcurrentQueue<T>

So if you call Peek from one thread while the queue is modified concurrently by other threads, without synchronizing properly the access to the queue using a lock or other means, you are breaking the guarantees offered by the manufacturer of the class. The behavior of the class becomes officially "undefined".因此,如果您在队列被其他线程同时修改时从一个线程调用Peek ,而没有使用lock或其他方式正确同步对队列的访问,那么您将破坏 class 制造商提供的保证。行为的行为class 正式变为“未定义”。 This is not a good idea if you are trying to make a program that should be reliable regarding the correctness of its results.如果您正在尝试制作一个在结果正确性方面应该可靠的程序,那么这不是一个好主意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM