Коллекция Threadsafe без блокировки

Я готовлюсь к собеседованию, и я наткнулся на следующий вопрос. Я попытался, но я не мог найти ничего, что могло бы создать класс, содержащий поточную безопасную коллекцию без "блокировки". Если знаете какое-либо решение, тогда, пожалуйста, помогите.

Создайте класс С#, полученный из Object, и реализуйте следующие методы:

  • AddString - этот метод должен добавить заданную строку во внутреннюю коллекцию
  • ToString - переопределите этот метод и верните одну строку с разделителями-запятыми, содержащую все строки во внутренней коллекции

Требования:

  • Должен быть потокобезопасным
  • Должен поддерживать несколько параллельных считывателей
  • Нельзя использовать существующие ранее потокобезопасные коллекции
  • Бонус: не используйте любой тип блокировки

Ответы

Ответ 1

Это способ добиться незакрепленной модификации коллекции, работая над локальной копией, а затем попытаться автоматически ее поменять на глобальную коллекцию, проверяя расы:

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 для неявного выполнения изменчивых чтений и записей вызвало некоторую путаницу, Im вместо менее эквивалентного кода вместо вызовов 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);
    }
}

Ответ 2

Вы можете создать неблокирующий связанный список. Например, что-то вроде this.

Структура .net предоставляет такие методы, как CompareExchange(Object, Object, Object), которые позволяют писать безопасный код без блокировки всего списка.

Ответ 3

Исходя из вопроса, вы должны иметь возможность добавлять параллельную коллекцию внутри своего объекта, которая будет обрабатывать требования безопасности потоков для вас. Они не указали, какой тип внутренней коллекции использовать.

Вы должны иметь возможность реализовать одну из коллекций из пространства имен concurrentcollection и достичь этого.

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

Ответ 4

Мое решение. В основном имитирует блокировку с помощью 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;
                }
            }
        }
    }

Ответ 5

Самое простое решение - это поле типа string[]. Всякий раз, когда вызывающий объект хочет добавить строку, создайте новый массив с добавленным новым элементом и замените его на старый.

Эта модель не требует синхронизации. Он не терпит одновременных авторов, но допускает параллельное чтение.