Я отвечаю здесь, чтобы добавить к ответу Марка, где он говорит, что «есть также очень много разных способов сделать что-то поточно-ориентированное, в зависимости от профиля доступа».
Я просто хочу добавитьОтчасти это объясняется тем, что существует множество способов, которыми не является поточно-ориентированным, поэтому, когда мы говорим, что-то является поточно-ориентированным, нам необходимо четко понимать, какая безопасность обеспечивается.
Практически с любым изменяемым объектом могут быть способы работы с ним, которые не являются поточно-ориентированными (примечание почти любое, возникает исключение).Рассмотрим потокобезопасную очередь, которая имеет следующие (потокобезопасные) члены;операция постановки в очередь, операция удаления из очереди и свойство count.Относительно легко создать один из них либо с помощью внутренней блокировки на каждом элементе, либо даже с использованием методов без блокировки.
Однако, скажем, мы использовали объект следующим образом:
if(queue.Count != 0)
return queue.Dequeue();
Приведенный выше код не является потокобезопасным, поскольку нет гарантии, что после (поточно-безопасного) Count
возврата 1, другой поток не выйдет из очереди и, следовательно, приведет к сбою второй операции.
Этово многих случаях он по-прежнему является потокобезопасным объектом, особенно если даже в этом случае сбоя операция сбоя очереди не переведет объект в недопустимое состояние.
Чтобы сделать объект полностью безопасным в потоке влицо любой данной комбинации операций, мы должны либо сделать ее логически неизменной (возможно иметь внутреннюю изменчивость с поточно-ориентированными операциями, обновляющими внутреннее состояние как оптимизацию - например, путем запоминания или загрузки из источника данных по мере необходимости, но извнеон должен казаться неизменным) или значительно уменьшить количество внешних операцийИоны возможны (мы могли бы создать потокобезопасную очередь, в которой были бы только Enqueue
и TryDequeue
, которая всегда поточно-ориентирована, но которая одновременно уменьшает возможные операции, а также вынуждает переопределять отказавшую очередь, как не являющуюся ошибкой,и вызывает изменение в логике вызова кода по сравнению с версией, которая была у нас ранее).
Все остальное является частичной гарантией.Мы получаем некоторые частичные гарантии бесплатно (как отмечает Марк, действие на некоторые автоматические свойства уже поточно-ориентировано в отношении индивидуальной атомарности - что в некоторых случаях обеспечивает всю необходимую нам безопасность потоков, но в других случаях никуда не денетсядостаточно далеко).
Давайте рассмотрим атрибут, который добавляет эту частичную гарантию в те случаи, когда мы его еще не получили.Насколько это ценно для нас?Ну, в некоторых случаях это будет прекрасно, а в других - нет.Возвращаясь к нашему случаю тестирования перед запуском, такая гарантия на Count
не очень полезна - у нас была такая гарантия, и код все еще не работал в многопоточных условиях, как это было бы в однопоточных условиях..
Более того, добавление этой гарантии к случаям, в которых ее еще нет, требует как минимум некоторой дополнительной нагрузки.Может быть преждевременной оптимизацией постоянно беспокоиться о накладных расходах, но добавление накладных расходов без какой-либо выгоды является преждевременной пессимизацией, поэтому не будем этого делать!Более того, если мы обеспечим более широкий контроль параллелизма, чтобы сделать набор операций действительно поточно-ориентированным, то мы сделаем узкие элементы управления параллелизмом неактуальными, и они станут чисто накладными расходами - поэтому мы даже не получим ценность из нашегонакладные расходы в некоторых случаях;это почти всегда чисто трата.
Также не ясно, насколько широки или узки проблемы параллелизма.Нужно ли нам блокировать (или подобное) только для этого свойства или нам нужно блокировать все свойства?Нужно ли блокировать также неавтоматические операции, и возможно ли это?
Здесь нет однозначного хорошего ответа (они могут быть сложными вопросами, на которые нужно ответить при развертывании собственного решения, не говоря уже о попыткахответьте на него в коде, который выдает такой код, когда кто-то еще использует этот атрибут [Threadsafe].
Кроме того, любой данный подход будет иметь другой набор условий, в которых могут возникать взаимоблокировки, livelock и подобные проблемы, поэтому мы можем фактически снизить безопасность потоков, рассматривая безопасность потоков как то, что мы можем просто слепо применять к свойству.
Не имея возможности найти единый универсальный ответ на эти вопросы, нет хорошего способа обеспечить единую универсальную реализацию, и любой такой атрибут [Threadsafe] в лучшем случае будет иметь очень ограниченную ценность.Наконец, на психологическом уровне программиста, использующего его, очень вероятно, что это приведет к ложному ощущению безопасности, что они создали потокобезопасный класс, хотя на самом деле это не так;что сделает его на самом деле хуже бесполезным.