Давайте ответим на заданный вопрос:
Будет ли JIT-компилятор по-прежнему оптимизировать код, как показано выше, если я заблокирую _flag или только сделаю его изменчивым, остановит оптимизацию?
Хорошо, давайте не будем отвечать на заданный вопрос, потому что этот вопрос слишком сложный.Давайте разберем его на ряд менее сложных вопросов.
Будет ли JIT-компилятор по-прежнему оптимизировать код, как показано выше, если я заблокирую _flag?
Краткий ответ: lock
дает более сильную гарантию, чем volatile
, поэтому нет, джиттеру не будет разрешено выводить показания из цикла, если имеется блокировка вокруг показания _flag
.Конечно, замок также должен быть вокруг записи .Блокировки работают, только если вы используете их везде .
private bool _flag = true;
private object _flagLock = new object();
public void Run()
{
new Thread(() => { lock(_flaglock) _flag = false; }).Start();
while (true)
lock (_flaglock)
if (!_flag)
break;
}
(И, конечно, я отмечаю, что это безумно плохой способ ожидания одного потока дляподайте сигнал другому. Никогда не сидите в узкой петле, опрашивая флаг! Используйте ручку ожидания как разумный человек.)
Вы сказали, что замки были сильнее летучих;что это значит?
Чтение в летучие компоненты предотвращает перемещение определенных операций во времени.Запись в volatiles предотвращает перемещение определенных операций во времени.Блокировки препятствуют своевременному перемещению больше операций.Эта семантика предотвращения называется «ограждением памяти» - в основном, летучие вещества вводят половину ограждения, замки вводят полное ограждение.
Подробнее см. Раздел спецификации C # по специальным побочным эффектам.
Как всегда, я напомню, что летучие вещества не дают глобальных гарантий свежести .В многопоточном программировании на C # нет такой вещи, как «последнее» значение переменной, и поэтому изменчивые чтения не дают вам «последнего» значения, потому что его не существует.Идея, что существует «последнее» значение, подразумевает, что чтение и запись всегда имеют глобально согласованный порядок во времени, и это неверно.Потоки могут по-прежнему расходиться во мнениях относительно порядка изменчивых операций чтения и записи.
Блокировки не позволяют выполнить эту оптимизацию;летучие вещества препятствуют этой оптимизации.Это единственное, что мешает оптимизации?
Нет.Вы также можете использовать операции Interlocked
или явно вводить ограничения памяти.
Я понимаю, достаточно ли этого для правильного использования volatile
?
Нет.
Что мне делать?
Во-первых, не писать многопоточные программы.Несколько потоков управления в одной программе - плохая идея.
Если вам необходимо, не разделяйте память между потоками.Используйте потоки в качестве недорогих процессов и используйте их только в том случае, если у вас неактивный ЦП, который может выполнять задачу с интенсивным ЦП.Используйте однопотоковую асинхронность для всех операций ввода-вывода.
Если вам необходимо совместно использовать память между потоками, используйте доступную вам программную конструкцию самого высокого уровня , а не низший уровень .Используйте CancellationToken
для представления операции, отменяемой в другом месте асинхронного рабочего процесса.