Threadsafe коллекция без блокировки
Я готовлюсь к собеседованию и натолкнулся на следующий вопрос. Я пытался, но я не смог найти ничего, что могло бы создать класс, содержащий потокобезопасную коллекцию без "блокировки". Если знаете какое-либо решение, пожалуйста, помогите.
Создайте класс C#, производный от Object, и реализует следующие методы:
- AddString - этот метод должен добавить данную строку во внутреннюю коллекцию.
- ToString - переопределить этот метод и вернуть одну строку с разделителями-запятыми, содержащую все строки во внутренней коллекции
Требования:
- Должен быть потокобезопасным
- Должен поддерживать несколько одновременных читателей
- Не должны использовать какие-либо ранее существующие поточно-ориентированные коллекции
- Бонус: не используйте любой тип блокировки
5 ответов
Вот способ добиться беспрепятственного изменения коллекции, работая с локальной копией, а затем пытаясь атомарно поменять ее с глобальной коллекцией, одновременно проверяя расы:
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);
}
}
Редактировать: С помощью Interlocked.CompareExchange
неявное выполнение нестабильных операций чтения и записи привело к некоторой путанице, я публикую ниже эквивалентный код с Thread.MemoryBarrier
звонки вместо
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);
}
}
Основываясь на этом вопросе, вы сможете добавить в свой объект параллельную коллекцию, которая будет отвечать требованиям безопасности потоков. Они не указали, какой тип внутренней коллекции использовать.
Вы должны иметь возможность реализовать одну из коллекций из пространства имен concurrentcollection и достичь этого.
http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx
Вы можете создать неблокирующий связанный список. Например что-то вроде этого.
.NET Framework предоставляет такие методы, как CompareExchange(Object, Object, Object)
это позволяет вам писать безопасный код, не блокируя весь список.
Мое решение. В основном имитируют блокировку с использованием Interlocked.Exchange и AutoResetEvents. Сделал несколько простых тестов и похоже работает.
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;
}
}
}
}
Самое простое решение - иметь поле типа string[]
, Всякий раз, когда вызывающий абонент хочет добавить строку, создайте новый массив с добавленным новым элементом и замените его на старый.
Эта модель не требует синхронизации. Он не терпит одновременных писателей, но допускает одновременное чтение.