27.04.06 15:20

Delphi

У Алены в посте про явные и скрытые особенности 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? Очень интересно...

Комментарии: 6 (особо ценных: 1) (feed)

  1. Alena

    Чтобы такого не происходило, переменную объявляют как volatile, что заставляет компилятор не применять к ней таких оптимизаций.

    Хочу добавить, что мой пост был в том числе и о том, что доступ к переменной из разных потоков - это некорректное использование volatile. Несмотря на то, что встречается оно довольно часто.

  2. AL-one

    (Хоть и не в тему) А автор не боится, что сейчас начнется "холли-вар" между делфистами и сиплюсплюсниками ? :)
    (В тему) Мне кажется, что делфийский оптимизатор достаточно умен, чтобы не трогать условия выхода из цикла.

  3. Elf

    Хм.. А я как-то и не задумывался никогда про компилятор и его особенности. Просто делал многопоточные программы и всё :-)
    Правда, и C++ я не знаю. Хотя, похоже, хорошо что не знаю, если там программист должен заботливо рассказывать компилятору сказки, что можно, а что нельзя делать с его кодом :)

    P.S. Это только у меня такие мелкие шрифты в комментах? Читать невозможно абсолютно. Только кнопкой "+" и спасаюсь... :)

  4. Иван Сагалаев

    Проверил идею с условием выхода -- не оно. В треде сделал такой цикл:

    var
      Value:String;
    
    ...
    
    while not Terminated do
    begin
      Form1.Label1.Caption:=Value;
    end;
    

    Переменная Value -- глобальная внутри модуля (под implementation определена). Меняю ее извне треда -- начинает присваиваться новое значение.

    Вот у меня другая идея возникла. Похоже, делфийский компилятор делает цикловые оптимизации только для локальных переменных в функции, что безопасно, потому что их извне поменять просто нельзя.

    P.S. Холиваров не боюсь :-). Не будет их.

  5. AL-one

    Скорее и то, и то. Т.е. компилятор не трогает нелокальные переменные и не трогает переменные в условиях цикла. Хотя есть такой пример:

    for i := 5 to 10 do
      OtherProc;
    

    Вот здесь переменная i пробегает значения от 6 до 0, т.к. она не используется внутри цикла, а с нулем сравнивать легче. Для нее даже не заводится место в стеке, а используется регистр процессора.

    Но опять-таки компилятор не тронет счетчик цикла, если он используется внутри процедуры OtherProc.

  6. enternet

    29.04.06 22:16

    Особо ценный комментарий

    2 Иван Сагалаев:
    Именно так. Компилятор в данном случае оптимизирует использование только локальных переменных.
    В общем случае - компилятор дельфийный достаточно "туп". Оптимизации там минимум. Я, например, несколько раз занимался декомпиляцией в исходный код 8-) Ответственно заявляю - похоже на табличную подстановку. НО! Не вижу ничего плохого в таком подходе. Из-за ограниченного набора используемых команд и их примитивной группировки дельфийный код выполняется современными процессорами замечательно быстро - конвейеры процессора загружаются как нужно. Это во времена 486 наблюдались проблемы с таким подходом. Но уже начиная с первых пентиумов (2 конвейера) правила игры кардинально поменялись. А с современной точки зрения оптимизация вовсе нафиг не нужна - её делают сами процессоры. 8-)

Добавить комментарий

Вы можете подписать комментарий своим OpenID-логином или именем с EMail'ом.

OpenID

Имя и EMail

Текст через пустую строку превращается в отдельные абзацы, цитата отделяется символами > слева, списка состоит из пунктов с дефисом слева, курсив выделяется * с каждой стороны, жирный - двойными **, блоки кода отступают слева на 4 пробела