问题
I am preparing myself for an interview and I came across the followign question. I tried but I could not find anything which can create a class containing thread safe collection without "lock". If know any solution then please help.
Create a C# class derived from Object and implements the following methods:
- AddString – This method should add a given string to an internal collection
- ToString – Override this method and return a single, comma-delimited string containing all the strings in the internal collection
Requirements:
- Must be thread-safe
- Must support multiple concurrent readers
- Must not use any pre-existing thread-safe collections
- Bonus: don’t use any type of lock
回答1:
Here’s a way of achieving lock-free modification of a collection by working on a local copy and then attempting to atomically swap it with the global collection whilst checking for races:
public class NonLockingCollection
{
private List<string> collection;
public NonLockingCollection()
{
// Initialize global collection through a volatile write.
Interlocked.CompareExchange(ref collection, new List<string>(), null);
}
public void AddString(string s)
{
while (true)
{
// Volatile read of global collection.
var original = Interlocked.CompareExchange(ref collection, null, null);
// Add new string to a local copy.
var copy = original.ToList();
copy.Add(s);
// Swap local copy with global collection,
// unless outraced by another thread.
var result = Interlocked.CompareExchange(ref collection, copy, original);
if (result == original)
break;
}
}
public override string ToString()
{
// Volatile read of global collection.
var original = Interlocked.CompareExchange(ref collection, null, null);
// Since content of global collection will never be modified,
// we may read it directly.
return string.Join(",", original);
}
}
Edit: Since using Interlocked.CompareExchange to implicitly perform volatile reads and writes has given rise to some confusion, I’m posting below the equivalent code with Thread.MemoryBarrier calls instead.
public class NonLockingCollection
{
private List<string> collection;
public NonLockingCollection()
{
// Initialize global collection through a volatile write.
collection = new List<string>();
Thread.MemoryBarrier();
}
public void AddString(string s)
{
while (true)
{
// Fresh volatile read of global collection.
Thread.MemoryBarrier();
var original = collection;
Thread.MemoryBarrier();
// Add new string to a local copy.
var copy = original.ToList();
copy.Add(s);
// Swap local copy with global collection,
// unless outraced by another thread.
var result = Interlocked.CompareExchange(ref collection, copy, original);
if (result == original)
break;
}
}
public override string ToString()
{
// Fresh volatile read of global collection.
Thread.MemoryBarrier();
var original = collection;
Thread.MemoryBarrier();
// Since content of global collection will never be modified,
// we may read it directly.
return string.Join(",", original);
}
}
回答2:
You could create a non blocking linked list. For example something like this.
The .net framework provides methods like CompareExchange(Object, Object, Object) that allow you to write safe code without locking the whole list.
回答3:
Based on the question you should be able to add a concurrent collection inside your object that will handle the thread safety requirements for you. They did not specify what type of internal collection to use.
You should be able to implement one of the collections from the concurrentcollection namespace and achieve this.
http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx
回答4:
My solution. Basically mimic locking using Interlocked.Exchange and an AutoResetEvents. Did some simple tests and it seems working.
public class SharedStringClass
{
private static readonly int TRUE = 1;
private static readonly int FALSE = 0;
private static int allowEntry;
private static AutoResetEvent autoResetEvent;
private IList<string> internalCollection;
public SharedStringClass()
{
internalCollection = new List<string>();
autoResetEvent = new AutoResetEvent(false);
allowEntry = TRUE;
}
public void AddString(string strToAdd)
{
CheckAllowEntry();
internalCollection.Add(strToAdd);
// set allowEntry to TRUE atomically
Interlocked.Exchange(ref allowEntry, TRUE);
autoResetEvent.Set();
}
public string ToString()
{
CheckAllowEntry();
// access the shared resource
string result = string.Join(",", internalCollection);
// set allowEntry to TRUE atomically
Interlocked.Exchange(ref allowEntry, TRUE);
autoResetEvent.Set();
return result;
}
private void CheckAllowEntry()
{
while (true)
{
// compare allowEntry with TRUE, if it is, set it to FALSE (these are done atomically!!)
if (Interlocked.CompareExchange(ref allowEntry, FALSE, TRUE) == FALSE)
{
autoResetEvent.WaitOne();
}
else
{
break;
}
}
}
}
回答5:
The easiest solution is having a field of type string[]. Whenever a caller wants to add a string, create a new array with the new item appended and swap it for the old one.
This model does not require synchronization. It does not tolerate concurrent writers but it allows for concurrent reading.
来源:https://stackoverflow.com/questions/10675400/threadsafe-collection-without-lock