假设你有一个内存中的字符串列表,以及一个多线程系统,有许多读者但只有一个编写器线程。
一般来说,是否可以在C#中实现这种系统而不使用锁?实现是否会对线程如何交互做出任何假设(或者限制它们可以做什么,什么时候做?)
答案 0 :(得分:25)
是。诀窍是确保列表保持不变。编写器将对主集合进行快照,修改快照,然后将快照发布到包含对主集合的引用的变量。以下示例演示了这一点。
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
的原因非常具体,为什么在读者端获取本地参考,等等。如果您不理解这些原因,请不要使用该模式。有太多可能出错的地方。由于上述限制,这对您有益的情况非常有限。最大的问题是写入首先要求完整拷贝,因此它们可能很慢。但是,如果写入很少,那么这可能是可以容忍的。
我在answer here中描述了更多模式,包括一个对多个作者都安全的模式。
答案 1 :(得分:8)
对于线程库来说,这是一个相当常见的请求 - 这种锁通常被称为“读写器锁”,或者是该主题的一些变体。我没有必要专门使用C#实现,但有一个:http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx
当然,你遇到的问题是,如果读者总是在阅读,你将永远无法让作家写作。我相信你必须自己处理。
(好吧,所以它在技术上仍然是一个“锁定”,但它不是C#“锁定”结构,它是一个更复杂的对象,专门为问题中所述的目的而设计。所以我猜这是否是一个正确的答案取决于某种程度上关于语义和他为什么问这个问题。)
答案 2 :(得分:8)
为避免锁定,您可能需要考虑Microsoft的concurrent collections。这些集合提供了对有序和无序表单中对象集合的线程安全访问。他们在尽可能多的情况下use some neat tricks to avoid locking internally。
答案 3 :(得分:6)
您还可以使用Microsoft的新Immutable Collections库:http://blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx
注意:这与Concurrent Collections完全分开。
答案 4 :(得分:1)
如果编写器只在头部或尾部插入/删除,则可以使用单链接列表方法而不使用锁定。在任何一种情况下,如果事先构造新节点,则只需要一个原子操作(head = newHead;或tail.next = newTail)来使操作对读者可见。
在性能方面,插入和删除是O(1),而长度计算是O(n)。