У Алены в посте про явные и скрытые особенности volatile
-переменных в C++ идет обсуждение того, почему этот volatile
необходим в многопоточных приложениях. И я задумался, почему его нет в Delphi, и как оно вообще без него работает.
В частности есть совершенно классическая модель работы thread'а, когда он что-то делает в цикле, и периодически проверяет, не надо ли экстренно прерваться:
procedure Execute;
begin
while not Terminated do
begin
...
end;
end;
Так вот в C++, если эта самая Terminated — обычная булевская переменная, то компилятор может внести оптимизацию. Видя, что переменная не меняется в цикле, он считает ее один раз в регистр и не будет каждый раз лезть за ней в память. И получается, что попытки изменить ее извне thread'а ничего не изменят, цикл будет крутиться вечно. Чтобы такого не происходило, переменную объявляют как volatile
, что заставляет компилятор не применять к ней таких оптимизаций.
В Delphi такого слова нет. И компилятор у Delphi весь из себя тоже оптимизирующий, и я где-то даже определенно встречал, что переменные из циклов он исключать умеет. И тем не менее, хоть я и писал довольно много многопоточного софта, но ни разу таких проблем не встречал. Почему?
У меня появилась одна теория. Ведь Terminated в Delphi — это не переменная, а метод, который читает пирватную FTerminated у класса TThread. И устанавливается она тоже не напрямую, а через метод Terminate. "Ага!" — подумал я, и написал пример, где вместо Terminated используется простая булевская переменная. Все равно работает!
Вот не знает ли кто, как Delphi обходится без volatile
? Очень интересно...
Комментарии: 7 (особо ценных: 1)
Хочу добавить, что мой пост был в том числе и о том, что доступ к переменной из разных потоков - это некорректное использование volatile. Несмотря на то, что встречается оно довольно часто.
(Хоть и не в тему) А автор не боится, что сейчас начнется "холли-вар" между делфистами и сиплюсплюсниками ? :)
(В тему) Мне кажется, что делфийский оптимизатор достаточно умен, чтобы не трогать условия выхода из цикла.
Хм.. А я как-то и не задумывался никогда про компилятор и его особенности. Просто делал многопоточные программы и всё :-)
Правда, и C++ я не знаю. Хотя, похоже, хорошо что не знаю, если там программист должен заботливо рассказывать компилятору сказки, что можно, а что нельзя делать с его кодом :)
P.S. Это только у меня такие мелкие шрифты в комментах? Читать невозможно абсолютно. Только кнопкой "+" и спасаюсь... :)
Проверил идею с условием выхода — не оно. В треде сделал такой цикл:
Переменная Value — глобальная внутри модуля (под implementation определена). Меняю ее извне треда — начинает присваиваться новое значение.
Вот у меня другая идея возникла. Похоже, делфийский компилятор делает цикловые оптимизации только для локальных переменных в функции, что безопасно, потому что их извне поменять просто нельзя.
P.S. Холиваров не боюсь :-). Не будет их.
Скорее и то, и то. Т.е. компилятор не трогает нелокальные переменные и не трогает переменные в условиях цикла. Хотя есть такой пример:
Вот здесь переменная i пробегает значения от 6 до 0, т.к. она не используется внутри цикла, а с нулем сравнивать легче. Для нее даже не заводится место в стеке, а используется регистр процессора.
Но опять-таки компилятор не тронет счетчик цикла, если он используется внутри процедуры OtherProc.
Особо ценный комментарий
2 Иван Сагалаев:
Именно так. Компилятор в данном случае оптимизирует использование только локальных переменных.
В общем случае - компилятор дельфийный достаточно "туп". Оптимизации там минимум. Я, например, несколько раз занимался декомпиляцией в исходный код 8-) Ответственно заявляю - похоже на табличную подстановку. НО! Не вижу ничего плохого в таком подходе. Из-за ограниченного набора используемых команд и их примитивной группировки дельфийный код выполняется современными процессорами замечательно быстро - конвейеры процессора загружаются как нужно. Это во времена 486 наблюдались проблемы с таким подходом. Но уже начиная с первых пентиумов (2 конвейера) правила игры кардинально поменялись. А с современной точки зрения оптимизация вовсе нафиг не нужна - её делают сами процессоры. 8-)
Эх, молодёж.. Ведь можно взять дизассемблер и прочитать, как в открытой книге.
Если #1 за-комментировано, в месте #2 Delphi генерирует:
Если же #1 не за-комментировано, то:
Эх, ну никого воображения.. зато вот ромашки дёргать все умеют- в регистре, не в регистре, в регистре, не в регистре.. ещё к гадалке сходите..