Блокировка по сравнению с ToArray для ориентированного на многопотоковое исполнение foreach доступа набора Списка

У меня есть набор Списка, и я хочу выполнить итерации по нему в многопоточном приложении. Я должен защитить его каждый раз, когда я выполняю итерации его, так как это могло быть изменено, и я не хочу "набор, был изменен" исключения, когда я делаю foreach.

Что корректный путь состоит в том, чтобы сделать это?

  1. Используйте блокировку каждый раз, когда я получаю доступ или цикл. Я скорее испуган мертвыми блокировками. Возможно, я просто параноик из использования блокировки и не должен быть. Что я должен знать, иду ли я этим путем для предотвращения мертвых блокировок? Действительно ли блокировка довольно эффективна?

  2. Используйте Список <>.ToArray () для копирования в массив каждый раз, когда я делаю foreach. Это вызывает хит производительности, но легко сделать. Я волнуюсь по поводу перегрузки памяти, а также время скопировать его. Просто кажется чрезмерным. Действительно ли это ориентировано на многопотоковое исполнение для использования ToArray?

  3. Не используйте foreach и использование для циклов вместо этого. Разве я не должен был бы делать проверки длины каждый раз, когда я сделал это, чтобы удостовериться, что список не уменьшался? Это кажется раздражающим.

22
задан Bryan Legend 27 June 2010 в 21:02
поделиться

5 ответов

Нет причин опасаться тупиковых ситуаций, их легко обнаружить. Ваша программа перестает работать, мертвая распродажа. Чего действительно стоит бояться, так это гонок потоков - типа ошибки, которую вы получите, если не заблокируете, когда нужно. Очень сложно диагностировать.

  1. Использование блокировки - это нормально, просто убедитесь, что вы используете один и тот же объект блокировки в любом коде, который касается этого списка. Как код, который добавляет или удаляет элементы из этого списка. Если этот код выполняется в том же потоке, который выполняет итерацию списка, вам не нужна блокировка. Как правило, единственный шанс для тупиковой ситуации здесь - это если у вас есть код, который полагается на состояние потока, например Thread.Join (), в то время как он также удерживает этот блокирующий объект. Что должно быть редкостью.

  2. Да, повторение копии списка всегда является потокобезопасным, если вы используете блокировку метода ToArray (). Обратите внимание, что вам все еще нужен замок, никаких структурных улучшений. Преимущество состоит в том, что вы удерживаете блокировку на короткое время, улучшая параллелизм в вашей программе. Недостатками являются требования к хранилищу O (n), наличие только безопасного списка, но без защиты элементов в списке, а также сложная проблема, связанная с постоянным устаревшим представлением содержимого списка. Последняя проблема особенно тонка и трудна для анализа. Если вы не можете объяснить побочные эффекты, вам, вероятно, не стоит об этом думать.

  3. Обязательно относитесь к способности foreach определять расу как к подарку, а не к проблеме. Да, явный цикл for (;;) не вызовет исключения, он просто выйдет из строя. Например, повторять один и тот же элемент дважды или полностью пропускать элемент. Вы можете избежать повторной проверки количества элементов, повторяя его в обратном порядке. Пока другие потоки вызывают только Add (), а не Remove (), которые будут вести себя аналогично ToArray (), вы получите устаревшее представление.Не то, чтобы это сработало на практике, индексация списка тоже не является поточно-ориентированной. List <> при необходимости перераспределит свой внутренний массив. Это просто не будет работать и работать непредсказуемо.

Здесь есть две точки зрения. Вы можете испугаться и следовать здравому смыслу, вы получите программу, которая работает, но может быть неоптимальной. Это мудро и делает босса счастливым. Или вы можете поэкспериментировать и выяснить для себя, как искажение правил приводит к неприятностям. Что сделает вас счастливым, вы станете гораздо лучшим программистом. Но ваша продуктивность пострадает. Я не знаю, как выглядит твой график.

43
ответ дан 29 November 2019 в 03:41
поделиться

Используйте lock(), если у вас нет другой причины для создания копий. Тупики могут возникнуть только в том случае, если вы запрашиваете несколько блокировок в разном порядке, например:

Thread 1:

lock(A) {
  // .. stuff
  // Next lock request can potentially deadlock with 2
  lock(B) {
    // ... more stuff
  }
}

Thread 2:

lock(B) {
  // Different stuff
  // next lock request can potentially deadlock with 1
  lock(A) {
    // More crap
  }
}

Здесь поток 1 и поток 2 потенциально могут вызвать тупик, поскольку поток 1 может удерживать A, а поток 2 удерживает B, и ни один из них не может продолжить работу, пока другой не освободит свою блокировку.

Если вы должны взять несколько блокировок, всегда делайте это в одном и том же порядке. Если вы берете только одну блокировку, то вы не вызовете тупика... если только вы не держите блокировку, ожидая ввода данных пользователем, но это технически не тупик и приводит к другому моменту: никогда не держите блокировку дольше, чем это абсолютно необходимо.

3
ответ дан 29 November 2019 в 03:41
поделиться

Если ваши данные List в основном предназначены только для чтения, вы можете разрешить нескольким потокам одновременно безопасно обращаться к ним с помощью ReaderWriterLockSlim

. Вы можете найти реализацию Thread-Safe Dictionary здесь чтобы вы начали.

Я также хотел упомянуть, что если вы используете .Net 4.0, класс BlockingCollection автоматически реализует эту функциональность. Хотел бы я знать об этом несколько месяцев назад!

11
ответ дан 29 November 2019 в 03:41
поделиться

В целом коллекции не являются потокобезопасными из соображений производительности, за исключением хэш-таблицы. Вы должны использовать IsSynchronized и SyncRoot, чтобы сделать их потокобезопасными. См. здесь и здесь

Пример из msdn

ICollection myCollection = someCollection;
lock(myCollection.SyncRoot)
{
    foreach (object item in myCollection)
    {
        // Insert your code here.
    }
}

Изменить: если вы используете .net 4.0, вы можете использовать одновременных коллекций

4
ответ дан 29 November 2019 в 03:41
поделиться

Вы также можете рассмотреть возможность использования неизменяемой структуры данных - относитесь к вашему списку как к типу значения.

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

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

public class ImmutableWidgetList : IEnumerable<Widget>
{
    private List<Widget> _widgets;  // we never modify the list

    // creates an empty list
    public ImmutableWidgetList()
    {
        _widgets = new List<Widget>();
    }

    // creates a list from an enumerator
    public ImmutableWidgetList(IEnumerable<Widget> widgetList)
    {
        _widgets = new List<Widget>(widgetList);
    }

    // add a single item
    public ImmutableWidgetList Add(Widget widget)
    {
        List<Widget> newList = new List<Widget>(_widgets);

        ImmutableWidgetList result = new ImmutableWidgetList();
        result._widgets = newList;
        return result;
    }

    // add a range of items.
    public ImmutableWidgetList AddRange(IEnumerable<Widget> widgets)
    {
        List<Widget> newList = new List<Widget>(_widgets);
        newList.AddRange(widgets);

        ImmutableWidgetList result = new ImmutableWidgetList();
        result._widgets = newList;
        return result;
    }

    // implement IEnumerable<Widget>
    IEnumerator<Widget> IEnumerable<Widget>.GetEnumerator()
    {
        return _widgets.GetEnumerator();
    }


    // implement IEnumerable
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _widgets.GetEnumerator();
    }
}
  • Я включил IEnumerable , чтобы разрешить реализацию foreach .
  • Вы упомянули, что беспокоитесь о пространственно-временной производительности создания новых списков, поэтому, возможно, это не сработает для вас.
  • вы также можете реализовать IList
5
ответ дан 29 November 2019 в 03:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: