假设您有一个内存中的字符串列表和一个多线程系统,有许多读取器,但只有一个写入器线程。
一般来说,是否可以在不使用锁的情况下在 C# 中实现这种系统?实现是否会对线程如何交互做出任何假设(或限制它们何时可以做什么)?
假设您有一个内存中的字符串列表和一个多线程系统,有许多读取器,但只有一个写入器线程。
一般来说,是否可以在不使用锁的情况下在 C# 中实现这种系统?实现是否会对线程如何交互做出任何假设(或限制它们何时可以做什么)?
是的。诀窍是确保列表保持不变。编写器将对主集合进行快照,修改快照,然后将快照发布到保存对主集合的引用的变量。下面的示例演示了这一点。
public class Example
{
// This is the immutable master collection.
volatile List<string> collection = new List<string>();
void Writer()
{
var copy = new List<string>(collection); // Snapshot the collection.
copy.Add("hello world"); // Modify the snapshot.
collection = copy; // Publish the snapshot.
}
void Reader()
{
List<string> local = collection; // Acquire a local reference for safe reading.
if (local.Count > 0)
{
DoSomething(local[0]);
}
}
}
这种方法有几个注意事项。
volatile
,为什么要在读者端获取本地引用等。如果您不了解这些原因,请不要使用该模式。有太多可能出错的地方。由于上述限制,这将使您受益的场景非常有限。最大的问题是写入首先需要完整副本,因此它们可能很慢。但是,如果写入不频繁,那么这可能是可以容忍的。
我在这里的答案中描述了更多模式,包括对多个作者安全的模式。
为避免锁定,您可能需要考虑 Microsoft 的并发集合。这些集合提供了对有序和无序形式的对象集合的线程安全访问。他们使用一些巧妙的技巧来避免在尽可能多的情况下进行内部锁定。
对于线程库来说,这是一个相当普遍的要求——这种锁通常被称为“读写器锁”,或者该主题的一些变体。我从来不需要专门使用 C# 实现,但有一个:http: //msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx
当然,您会遇到这样一个问题,即如果读者一直在阅读,您将永远无法让作者参与写作。我相信你必须自己处理。
(好吧,所以它在技术上仍然是一个“锁”,但它不是 C#“锁”结构,它是一个更复杂的对象,专门为问题中所述的目的而设计。所以我猜它是否是一个正确的答案在某种程度上取决于语义和关于他为什么要问这个问题。)
您还可以使用 Microsoft 新的不可变集合库:http: //blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx
注意:这与并发集合完全分开。
A singly-linked-list approach can be used without locks provided the writer only inserts/deletes at either the head or the tail. In either case, if you construct the new node beforehand, you only need a single atomic operation (head = newHead; or tail.next = newTail) to make the operation visible to the readers.
In terms of performance, insertions and deletions are O(1), while length calculation is O(n).