Вчера разговаривал по телефону со своим старым другом, который вот уже 15 лет живет и работает в США. Рассказал ему о споре в этой ветке форума. Он в ответ рассказа мне историю, которая произошла в одном из крупнейших американских банков несколько лет тому назад. Эта история напрямую не связана с перестановкой команд, но имеет прямое отношение к подводным камням многопоточности.
Итак, группа программистов реализовывала на Java серверное приложение, которое запускало внутри себя несколько потоков. В основном потоке была объявлена переменная (например int counter = 0).
Каждый порожденный поток при запуске вызывал функцию синхронизированного инкремента переменной counter на 1, а при окончании работы аналогичную функцию декремента на 1. При этом основной поток зависал примерно в таком цикле ожидания:
Код:
while(counter > 0)
{
// Делаем что-то и ждем пока завершатся все порожденные потоки
}
Ребята совершенно справедливо решили, что для изменения значения счетчика необходимо писать функции синхронизированного инкремента(декремента) в порожденных потоках. Но в основном потоке, подумали они, раз всего лишь читается значение переменной счетчика, то ни о чем беспокоится не надо, все будет - OK. Так вот это приложение подвисало 2-3 раза в день. Боролись с этим месяц, пока им не подсказали объявить counter, как volatile.
А причина проста - кеширование значения счетчика перед выполнением цикла в основном потоке. Например, JIT компилятор Java, мог запросто помещать значение counter в регистр, видя что его значение внутри цикла не изменяется, но часто читается.