Что подразумевается под «потокобезопасным» объектом? - PullRequest
6 голосов
/ 12 октября 2009

Я использовал общую очередь в коллекции C #, и все говорят, что лучше использовать объект System.Collection.Generic.Queue из-за безопасности потока.

Посоветуйте, пожалуйста, правильное решение об использовании объекта Queue и насколько он безопасен для потоков?

Ответы [ 3 ]

26 голосов
/ 12 октября 2009

«Потокобезопасный» - это досадный термин, потому что у него нет четкого определения. По сути, это означает, что определенные операции над объектом гарантированно ведут себя разумно, когда объект работает через несколько потоков.

Рассмотрим простейший пример: счетчик. Предположим, у вас есть два потока, которые увеличивают счетчик. Если последовательность событий идет:

  • Нить, которую читает со счетчика, получает ноль.
  • Тема два читает из счетчика, получает ноль.
  • Поток один увеличивает ноль, пишет один, чтобы противостоять.
  • Поток два шага ноль, пишет один на счетчик.

Тогда обратите внимание, как счетчик «потерял» одно из приращений. Простые операции приращения на счетчиках не являются потокобезопасными; чтобы сделать их потокобезопасными, вы можете использовать блокировки или InterlockedIncrement.

Аналогично с очередями. Не-потокобезопасные очереди могут «потерять» очереди так же, как счетчики, не поддерживающие нить, могут потерять приращения. Хуже того, небезопасные очереди могут даже аварийно завершить работу или привести к сумасшедшим результатам, если вы неправильно используете их в многопоточном сценарии.

Сложность с «потокобезопасностью» заключается в том, что она не четко определена. Означает ли это просто "не потерпит крах"? Значит ли это, что будут получены разумные результаты? Например, предположим, что у вас есть «потокобезопасная» коллекция. Этот код правильный?

if (!collection.IsEmpty) Console.WriteLine(collection[0]);

Нет. Даже если коллекция является «потокобезопасной», это не означает, что этот код правильный; другой поток мог сделать коллекцию пустой после проверки, но до записи, и, следовательно, этот код мог аварийно завершиться, даже если объект якобы является «потокобезопасным». На самом деле определить, что каждая соответствующая комбинация операций является поточно-ориентированной, чрезвычайно сложно.

Теперь перейдем к вашей реальной ситуации: тот, кто говорит вам: «Вы должны использовать класс Queue, лучше, потому что он потокобезопасен», вероятно, не имеет четкого представления о том, о чем он говорит. Во-первых, очередь не является потокобезопасной. Во-вторых, не имеет значения, является ли очередь потокобезопасной или нет, если вы используете объект только в одном потоке! Если у вас есть коллекция, доступ к которой будет осуществляться в нескольких потоках, то, как я указал в моем примере выше, вам будет чрезвычайно трудно решить проблему, независимо от того, является ли сама коллекция «поточно-безопасной». Вы должны определить, что каждая комбинация операций , которую вы выполняете с коллекцией, также является потокобезопасной. Это очень сложная проблема, и если с ней вы столкнулись, вам следует воспользоваться услугами эксперта по этой сложной теме.

5 голосов
/ 12 октября 2009

Тип, который является потокобезопасным, можно безопасно получить из нескольких потоков, не заботясь о параллелизме. Обычно это означает, что тип доступен только для чтения.

Интересно, что Queue<T> является не поточно-безопасным - он может поддерживать одновременное чтение, пока очередь не изменена, но это не то же самое, что безопасность потока.

Чтобы подумать о безопасности потоков, подумайте о том, что произойдет, если два потока будут обращаться к Queue<T>, а третий поток придет и начнет добавлять или удалять из этого Queue<T>. Поскольку этот тип не ограничивает это поведение, он не является потокобезопасным.

1 голос
/ 12 октября 2009

При работе с многопоточностью вам обычно приходится сталкиваться с проблемами параллелизма. Термин «проблемы параллелизма» относится к проблемам, которые конкретно связаны с возможностью чередования инструкций из двух разных контекстов выполнения на ресурсе, совместно используемом обоими. Здесь, с точки зрения безопасности потоков, контексты выполнения - это два потока внутри процесса; однако в смежных предметах это могут быть процессы.

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

Обычно объекты, доступные только для чтения, являются потокобезопасными. Многие объекты, которые не предназначены только для чтения, могут иметь доступ к данным (только для чтения) без проблем, если объект не модифицирован в середине. Но это не безопасность потока. Потокобезопасность - это когда все делается для типа данных, чтобы любые модификации одного потока не приводили к повреждению или взаимоблокировке данных даже при работе с множеством одновременных операций чтения и записи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...