IEnumerable <T> безопасность потоков?
У меня есть основной поток, который заполняет List<T>
. Далее я создаю цепочку объектов, которые будут выполняться в разных потоках, требуя доступа к списку. Первоначальный список никогда не будет записан после его создания. Моя мысль заключалась в том, чтобы передать список как IEnumerable<T>
для объектов, выполняющихся на других потоках, главным образом из-за того, что они не позволяют тем, кто реализует эти объекты, записывать в список по ошибке. Другими словами, если исходный список гарантированно не записывается, безопасно ли для нескольких потоков использовать .Where
или foreach
в IEnumerable?
Я не уверен, что итератор сам по себе является потокобезопасным, если исходная коллекция никогда не изменяется.
Ответы
Ответ 1
IEnumerable<T>
не может быть изменен. Итак, что может быть с ним небезопасным? (Если вы не изменяете фактический List<T>
).
Для обеспечения безопасности без потоков вам понадобятся операции записи и чтения.
"Итератор сам по себе" создается для каждого foreach
.
Изменить: Я немного упростил свой ответ, но @Eric Lippert добавил ценный комментарий. IEnumerable<T>
не определяет методы модификации, но это не означает, что операторы доступа являются потокобезопасными (GetEnumerator
, MoveNext
и т.д.). Простейший пример: GetEnumerator
реализован как это:
- Каждый раз возвращает тот же экземпляр
IEnumerator
- Сбрасывает позицию
Более сложным примером является кэширование.
Это интересный момент, но, к счастью, я не знаю какого-либо стандартного класса, который не имеет потокобезопасной реализации IEnumerable
.
Ответ 2
Каждый поток, который вызывает Where или foreach, получает свой собственный счетчик - они не делят один объект перечислителя для того же списка. Так как список не изменяется, и поскольку каждый поток работает со своей собственной копией перечислителя, не должно быть проблем с безопасностью потока.
Вы можете видеть, что это работает в одном потоке. Просто создайте список из 10 объектов и получите два перечисления из этого списка. Используйте один перечислитель для перечисления по 5 элементам, а другой - для перечисления по 5 элементам. Вы увидите, что оба перечисления перечислили только первые 5 элементов, а второй не запустился, когда первый счетчик остановился.
Ответ 3
Пока вы уверены, что List
никогда не будет изменен, тогда будет безопасно читать из нескольких потоков. Это включает использование экземпляров IEnumerator
, которые он предоставляет.
Это будет справедливо для большинства коллекций. Фактически, все коллекции в BCL должны быть стабильными во время перечисления. Другими словами, перечислитель не будет изменять структуру данных. Я могу подумать о некоторых неясных случаях, например, о splay-tree, перечислять, что он может изменить структуру. Опять же, ни одна из коллекций BCL не делает этого.
Ответ 4
Если вы уверены, что список не будет изменен после создания, вы должны гарантировать, что путем преобразования его в ReadOnlyCollection <T> . Конечно, если вы сохраните исходный список, который используется только для чтения, вы можете его изменить, но если вы выбросите исходный список, вы эффективно сделаете его permentantly только для чтения.
В разделе "Безопасность потока" коллекции:
ReadOnlyCollection может поддерживать несколько считывателей одновременно, пока сбор не изменяется.
Итак, если вы не коснетесь первоначального списка еще раз и прекратите ссылаться на него, вы можете убедиться, что несколько потоков могут прочитать его без беспокойства (пока вы не делаете ничего пугающего с попыткой изменить его снова).
Ответ 5
Другими словами, если исходный список гарантированно не записывается, безопасно ли использовать несколько потоков .Where или foreach на IEnumerable?
Да, это только проблема, если список мутирован.
Но обратите внимание, что IEnumerable<T>
можно отбросить в список и затем изменить.
Но есть и другая альтернатива: заверните свой список в ReadOnlyCollection<T>
и передайте его. Если вы сейчас выбросите исходный список, вы в основном создали новый неизменный список.
Ответ 6
Если вы используете net framework 4.5 или выше, это может быть отличным способом
http://msdn.microsoft.com/en-us/library/dd997305 (v = vs .110).aspx
(Microsoft уже реализовала потокобезопасное перечисление)