AutoResetEvent против логического, чтобы остановить поток
У меня есть объект в рабочем потоке, который я могу дать команду прекратить работу. Я могу реализовать это с помощью bool или AutoResetEvent:
логическое значение:
private volatile bool _isRunning;
public void Run() {
while (_isRunning)
{
doWork();
Thread.Sleep(1000);
}
}
AutoResetEvent:
private AutoResetEvent _stop;
public void Run() {
do {
doWork();
} while (!_stop.WaitOne(1000));
}
Stop()
метод будет затем установить _isRunning
ложь или вызов _stop.Set()
,
Кроме того, решение с AutoResetEvent может остановиться немного быстрее, есть ли разница между этими методами? Один "лучше", чем другой?
5 ответов
C# volatile не предоставляет всех гарантий. Это все еще может прочитать устаревшие данные. Лучше использовать базовый механизм синхронизации ОС, поскольку он обеспечивает гораздо более строгие гарантии.
Все это подробно обсуждается Эриком Липпертом (действительно стоит прочитать), вот короткая цитата:
В C# "volatile" означает не только "убедитесь, что компилятор и джиттер не выполняют переупорядочение кода или не регистрируют оптимизацию кэширования для этой переменной". Это также означает "сказать процессорам делать все, что им нужно, чтобы убедиться, что я читаю последнее значение, даже если это означает остановку других процессоров и синхронизацию основной памяти с их кешами".
На самом деле, этот последний бит является ложью. Истинная семантика изменчивых операций чтения и записи значительно сложнее, чем я описал здесь; фактически они не гарантируют, что каждый процессор останавливает свою работу и обновляет кэши в / из основной памяти. Скорее, они предоставляют более слабые гарантии того, что доступ к памяти до и после чтения и записи может быть упорядочен относительно друг друга. Определенные операции, такие как создание нового потока, ввод блокировки или использование одного из методов семейства Interlocked, предоставляют более строгие гарантии наблюдения за порядком. Если вам нужны подробности, прочитайте разделы 3.10 и 10.5.3 спецификации C# 4.0.
Честно говоря, я отговариваю вас от создания нестабильного поля. Изменчивые поля являются признаком того, что вы делаете что-то совершенно безумное: вы пытаетесь прочитать и записать одно и то же значение в двух разных потоках, не устанавливая блокировку на месте. Блокировки гарантируют, что считывание или изменение памяти внутри блокировки считается согласованным, блокировки гарантируют, что только один поток одновременно обращается к данному фрагменту памяти, и так далее.
Волатильность недостаточно хороша, но практически она всегда будет работать, потому что планировщик операционной системы всегда будет блокироваться. И будет хорошо работать на ядре с сильной моделью памяти, такой как x86, которая сжигает много сока, чтобы синхронизировать кэши между ядрами.
Так что на самом деле имеет значение только то, как быстро поток ответит на запрос на остановку. Это легко измерить, просто запустите секундомер в потоке управления и запишите время после цикла while в рабочем потоке. Результаты, которые я измерил по повторению взятия 1000 проб и усреднению, повторили 10 раз:
volatile bool, x86: 550 nanoseconds
volatile bool, x64: 550 nanoseconds
ManualResetEvent, x86: 2270 nanoseconds
ManualResetEvent, x64: 2250 nanoseconds
AutoResetEvent, x86: 2500 nanoseconds
AutoResetEvent, x64: 2100 nanoseconds
ManualResetEventSlim, x86: 650 nanoseconds
ManualResetEventSlim, x64: 630 nanoseconds
Помните, что результаты для volatile bool вряд ли будут хорошо смотреться на процессоре со слабой моделью памяти, такой как ARM или Itanium. У меня нет одного, чтобы проверить.
Очевидно, что вы хотите отдать предпочтение ManualResetEventSlim, что дает хорошую производительность и гарантию.
Одно замечание с этими результатами, они были измерены с рабочим потоком, выполняющим горячий цикл, постоянно проверяющий состояние остановки и не выполняющий никакой другой работы. Это не совсем хорошее соответствие с реальным кодом, поток обычно не проверяет условие остановки так часто. Что делает различия между этими методами в значительной степени несущественными.
ИМХО AutoResetEvent
лучше, потому что вы не можете забыть решающее volatile
Ключевое слово в этом случае.
Перед использованием volatile
ключевое слово, вы должны прочитать это, и, я думаю, при исследовании многопоточности вы можете прочитать всю статью http://www.albahari.com/threading/.
Это объясняет тонкости volatile
ключевое слово и почему его поведение может быть неожиданным.
Вы заметите, что при использовании volatile
, операции чтения и записи могут быть переупорядочены, что может привести к дополнительной итерации в близко параллельных ситуациях. В этом случае вам может потребоваться подождать еще одну секунду.
Посмотрев, я не думаю, что ваш код работает по нескольким причинам,
Фрагмент "boolean:" всегда спит примерно секунду, вероятно, не то, что вы хотите.
Фрагмент "AutoResetEvent:" не был создан _stop
и всегда будет работать doWork()
Хотя бы один раз.
Это зависит от того, что вы делаете, - это фрагмент кода выше. Событие AutoReset, как следует из его названия, сбрасывается после прохождения WaitOne. Это означает, что вы можете использовать его снова сразу, а не с bool, чтобы вернуть его в значение true.