На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! ПРАВИЛА РАЗДЕЛА · FAQ раздела Delphi · Книги по Delphi
Пожалуйста, выделяйте текст программы тегом [сode=pas] ... [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.
Следующие вопросы задаются очень часто, подробно разобраны в FAQ и, поэтому, будут безжалостно удаляться:
1. Преобразовать переменную типа String в тип PChar (PAnsiChar)
2. Как "свернуть" программу в трей.
3. Как "скрыться" от Ctrl + Alt + Del (заблокировать их и т.п.)
4. Как прочитать список файлов, поддиректорий в директории?
5. Как запустить программу/файл?
... (продолжение следует) ...

Вопросы, подробно описанные во встроенной справочной системе Delphi, не несут полезной тематической нагрузки, поэтому будут удаляться.
Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы. Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.


Внимание
Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
Повторная попытка - 60 дней. Последующие попытки бан.
Мат в разделе - бан на три месяца...
Модераторы: jack128, D[u]fa, Shaggy, Rouse_
Страницы: (5) « Первая ... 2 3 [4] 5  все  ( Перейти к последнему сообщению )  
> TThread.Synchronize
    Если адрес переменной не выравнен на ее размер, то атомарность чтения\записи не гарантируется. Хотя в x86 это проявляется только в случае пересечения границ кэш-линеек (64 байта).
      Цитата Jin X @
      У меня вот тут вопрос возник: будет ли обычное 32-битное присвоение (mov [X],eax), если переменная не выровнена по границе памяти, атомарным?

      Во-во-во, Вы уже начинаете задаваться правильными вопросами :victory:

      Чтобы заинтриговать, добавлю, что даже если операции были бы атомарны, это всё равно НЕ гарантировало бы правильности результата, по причине... Да-да, т.е. операции по отдельности потокобезопасны, но если собрать их вместе, получается чехарда. Вопрос: кто в этом виноват?
        Цитата Mr.Delphist @
        Чтобы заинтриговать, добавлю, что даже если операции были бы атомарны, это всё равно НЕ гарантировало бы правильности результата, по причине... Да-да, т.е. операции по отдельности потокобезопасны, но если собрать их вместе, получается чехарда. Вопрос: кто в этом виноват?
        Не понял мысли.
        Почему чехарда? Одна атомарная операция обеспечивает вход в критическую секцию, а дальше делай что хочешь, какие проблемы?
        Собственно, вопрос про mov в том, сил может, лучше использовать залоченную инструкцию, скажем, xchg (InterlockedXXX)?
          Если в деле участвуют критические секции, то в самом деле чехарды не будет. Но если просто понадеяться на факт атомарности чтения/записи и попытаться сделать на этом логику программы, то ой: данный критерий является необходимым, но вовсе не достаточным.
            Цитата Jin X @
            Я добавил Sleep(1), чтобы снизить нагрузку на процессор, потому что иначе цикл грузит его на максимум (Sleep(0) тоже не помогает).
            Так вообще корректно делать?

            Что именно? Использовать Sleep не только корректно, но и необходимо, чтобы не греть попусту процессор в ожидании у моря погоды. А вот использовать spin-wait loop на больших промежутках времени - это э-э-э... мягко говоря, кривой дизайн. Спин-лупы используются только на коротких интервалах с небольшим числом повторений (исчисляемых максимум десятками, но никак не десятками тысяч, как в твоем "идеальном" HiWord(i) = 0). В противном случае, они не только попусту нагружают и греют процессор, но и могут "тормозить сами себя" - если второй поток находится в ожидании свободного процессора, или выполняется на том же ядре с HT, то длительный спин-луп лишь затягивает запуск или тормозит исполнение этого потока.

            Цитата Jin X @
            Инструкция pause, как пишут, повышает производительность процессора за счёт оптимизация работы процессора с памятью и уменьшения мощности процессора (потребления энергии).
            Однако на загрузку процессора это не влияет.

            Во-первых, pause влияет на реальную загрузку процессора (с точки зрения Intel), т.к. проц не молотит с бешеной скоростью бестолковые инструкции по несколько штук за такт, а вставляет между ними паузы. Собственно в этом и смысл spin-wait - выдержать некоторую разумную паузу, не передавая управление ОС, но и не перегревая проц бестолковыми операциями. А с точки зрения ОС загрузка процессора конкретным потоком уменьшается только тогда, когда поток спит в недрах ОС, причем счет времени сна ведется довольно "топорно" - по тикам сис.таймера с дефолтным интервалом в ~15 мс.
            Во-вторых, обрати внимание, что в примерах Intel pause используется в сочетании с простыми операциями сравнения (cmp в 5.1 и (i & SPIN_MASK) == 0 в 5.2), которые могут выполняться в цикле с огромной скоростью (с перекрытием итераций за счет внеочередного\спекулятивного исполнения). Поэтому pause тут реально помогает снизить частоту (бестолковых) проверок. Ты же используешь pause в сочетании непосредственно с InterlockedExchange без доп.простых проверок, поэтому эффективность pause у тебя гораздо ниже, т.к. lock-операции сами по себе ужасно тормозные, и несколько тактов pause не идут ни в какое сравнение с сотней-другой тактов на InterlockedExchange.

            PS: Такое ощущение, что ты продолжаешь изобретать велосипеды в стиле "Саги об X, Y и Z". Вместо использования готовых объектов синхронизации (типа критической секции), используешь интерлоки где нужно и не нужно (например, в SetNotyfy), а в итоге все равно можешь нарваться на грабли (например, в Close, если промахнешься с таймаутом и выставишь FreeOnTerminate уже завершенному потоку).
              Цитата leo @
              Что именно? Использовать Sleep не только корректно, но и необходимо, чтобы не греть попусту процессор в ожидании у моря погоды. А вот использовать spin-wait loop на больших промежутках времени - это э-э-э... мягко говоря, кривой дизайн. Спин-лупы используются только на коротких интервалах с небольшим числом повторений (исчисляемых максимум десятками, но никак не десятками тысяч, как в твоем "идеальном" HiWord(i) = 0). В противном случае, они не только попусту нагружают и греют процессор, но и могут "тормозить сами себя" - если второй поток находится в ожидании свободного процессора, или выполняется на том же ядре с HT, то длительный спин-луп лишь затягивает запуск или тормозит исполнение этого потока.
              Ну ок, HiWord, допустим, это много, но...
              Цитата leo @
              А вот использовать spin-wait loop на больших промежутках времени - это э-э-э... мягко говоря, кривой дизайн.
              почему кривой, если он разбавлен Sleep(1)? Разве что сам Sleep(1) мне не очень нравится.
              Ну ещё хорошо бы locked-операции разбавить обычными - это я уже понял.

              Цитата leo @
              Вместо использования готовых объектов синхронизации (типа критической секции), используешь интерлоки где нужно и не нужно (например, в SetNotyfy)
              Почему же не нужно?
              Если SetNotify будет менять переменную FNotify в тот момент, когда её будет читать DoNotify (для исполнения), может получиться конфуз. Т.к. SetNotify изменила тип действия, но ещё не изменила адрес (процедуры и пр.), а DoNotify уже её прочитала.

              Цитата leo @
              а в итоге все равно можешь нарваться на грабли (например, в Close, если промахнешься с таймаутом и выставишь FreeOnTerminate уже завершенному потоку).
              Согласен. Как минимум DoNotify лучше перенести ниже, чем FreeOnTerminate и... ну не знаю, посоветуй тогда что-нибудь, как лучше сделать в этом месте? :)

              Добавлено
              Цитата leo @
              Во-первых, pause влияет на реальную загрузку процессора (с точки зрения Intel)
              Ок, какую-то внутрненюю разгрузку он, может, и делает, но если запустить
              ExpandedWrap disabled
                  asm
                    xor ecx,ecx
                  @:pause
                    loop @
                  end;
              и посмотреть на загрузку в диспетчере задач, то загруз там будет максимальным... Может, конечно, это с той точки зрения, что ядро полностью занято текущим потоком...

              Добавлено
              Хотя, я сейчас запустил 8 потоков с @:jmp @ и 8 потоков с @:pause jmp @ и посмотрел на температуру - нагрев одинаковый, что в том случае, что в другом. И тормоза системы одинаковые.
              Так что, может какие-то процессы он там и оптимизирует, а вот по поводу разгрузки вопрос...
                Pavia на другом форуме написал:
                Цитата
                Думаю что это будет оптимальнее, хотя и не всегда. С начало 1000 раз Sleep(0), а потом уже Sleep(1).
                Sleep(0)- нужен для микросекунд. Вернее мы сможем выиграть в производительности на микросекундном диапазоне. Процессор это не разгрузит, а вот производительности добавит. А вот Sleep(1) уже разгрузит процессор.

                За счёт чего выигрыш?
                Принудительно смена потоков происходит раз 10-250 мс. В некоторых случаях 1 мс.
                Первый поток ждет пока второй поток завершит работу, а второй поток не работает так как квант времени занят первым. Поэтому мы должны сами спровоцировать переключения контекстов иначе так мы будем ждать минимум 10 мс! До принудительного переключения. Что очень долго.

                Почему ОС не перераспределяет по ядрам потоки? Из-за энергосбережения она делает это редко. ОС в среднем перераспределяет процессы по ядрам если какой либо работает(висит) дольше 125 или 250 мс (не помню точно вроде второе число).

                Я создал 10 потоков и у каждого в цикле написал Sleep(0) потом поменял на SwitchToThread. Результат аналогичен Process Explorer показал 5 милионов переключений на 8 поточном процессоре. 1,6 мкс на переключение.
                А если сделать sleep(1) то от 300 до 1000 раз. Как видите разница на 3 порядка!

                Я ответил:
                Цитата
                Так оно и понятно, что будет на 3 порядка меньше. Ведь Sleep(1) делает ещё и задержку.

                Тогда, наверное, стоит сделать так: X (скажем, 256) полноценных циклов с pause, затем SwitchToThread. Всё это повторяем Y раз (скажем, 4), затем заменяем SwitchToThread на Sleep(1). И всё заново.
                Получается один Sleep(1) на 1024 циклов, но до него ещё 3 SwitchToThread.

                Я потестил этот механизм. И на i5 2500K, и на нетбуке со слабым процем (Atom) такой код с 256 потоками грузит проц меньше, чем на 1% (примерно такая же история с 1024 потоками при кол-ве циклов до Sleep(1) 32*4=256).
                p.s. Слишком частый вызов SwitchToThread тоже добавляет загрузки процу. Например 32*32 будет уже грузит i5 на 40-50% (256 потоков).

                Что думаете? :)

                Добавлено
                Или вот:
                ( ( 265 spin-циклов, затем SwitchToThread ) * 4 раза, затем Sleep(1) ) * 4 раза.
                Итого выходит 4096 циклов. Не так много, но и не мало.
                После этого переходим на Sleep(1) после каждой проверки, максимально разгружая таким образом проц.

                Добавлено
                Хочется соптимизировать вот эти числа: 256, 4 и 4 :)
                  Цитата Jin X @
                  Думаю что это будет оптимальнее, хотя и не всегда. С начало 1000 раз Sleep(0), а потом уже Sleep(1).
                  Sleep(0)- нужен для микросекунд. Вернее мы сможем выиграть в производительности на микросекундном диапазоне. Процессор это не разгрузит, а вот производительности добавит. А вот Sleep(1) уже разгрузит процессор.


                  Микросекунды на Win32 API?! Господа, не надо так хорошо думать о Билли Г :)
                  А если серьёзно, то идём и читаем маны: https://msdn.microsoft.com/en-us/library/wi...8(v=vs.85).aspx
                  Цитата
                  A value of zero causes the thread to relinquish the remainder of its time slice to any other thread that is ready to run. If there are no other threads ready to run, the function returns immediately, and the thread continues execution.


                  Т.е. нулевой интервал просто заставляет планировщик задач досрочно переключить контекст, не дожидаясь истечения запланированного кванта времени (обычно порядка 20-50 миллисекунд). Всё, точка, никаких микросекунд (столь мелкие интервалы появились лишь в multimedia API и по сути и будут спин-локами).

                  Update: ах да, ещё про спин-локи: они также были добавлены в функции по захвату критических секций и прочих примитивов, когда стала популярна многоядерность. Т.е. если раньше при сигнале "занято" был безусловный выход из попытки захвата ресурса, то позже туда добавили спин-лок и вторую попытку захвата:
                  * ой, занято
                  * подождём на спин-локе немножко, вдруг занято другим ядром и ненадолго
                  * ой, опять занято - видать, это надолго или нашим же ядром залочено, выходим
                  Сообщение отредактировано: Mr.Delphist -
                    Цитата Jin X @
                    Почему же не нужно?
                    Если SetNotify будет менять переменную FNotify в тот момент, когда её будет читать DoNotify

                    У тебя SetNotify вызывается до создания потока...

                    Цитата Jin X @
                    Согласен. Как минимум DoNotify лучше перенести ниже, чем FreeOnTerminate и... ну не знаю, посоветуй тогда что-нибудь, как лучше сделать в этом месте?

                    Уже посоветовал простой вариант - использовать FreeOnTerminate:=true и критическую секцию для проверки\установки FResult и посылки PostThreadMessage.
                    Хочешь не примитивную секцию, а со спин-лупом - пожалуйста, юзай InitializeCriticalSectionAndSpinCount или SetCriticalSectionSpinCount. Всё уже за тебя и для тебя сделано ребятами из MS - зачем изобретать самопальные велосипеды?

                    Цитата Jin X @
                    но если запустить ... и посмотреть на загрузку в диспетчере задач, то загруз там будет максимальным... Может, конечно, это с той точки зрения, что ядро полностью занято текущим потоком...

                    Не может, а точно. Тебе уже объяснили, что "загруз" в диспетчере задач - это не реальный "вычислительный загруз" процессора, а процент времени, когда поток подключен к процессору, а не спит в недрах ОС. Поэтому, не отдавая управление ОС функциями Sleep, Wait... и т.п., снизить этот "диспетчерский загруз" невозможно. Хоть pause используй, хоть hlt (если сможешь), ОС по любому будет считать, что твой поток работает и "грузит" процессор. Наглядный тому пример, процесс "Бездействие системы" в диспетчере задач, который в отсутствии гипер-активных процессов\потоков "грузит" проц почти на 100% - ниче так "бездействие"?! :)

                    Цитата Jin X @
                    Так что, может какие-то процессы он там и оптимизирует, а вот по поводу разгрузки вопрос...

                    Сдалась тебе эта загрузка\разгрузка? При однократном (или 4-х кратном) вызове Sleep(1) ты все равно никакой "разгрузки" в диспетчере не успеешь заметить (глазом моргнуть). Дело не в загрузке, а в целесообразности\оптимальности - спин-луп имеет смысл крутить короткое время в предположении, что другой поток "вот-вот" освободит лок. Если это крит.секция, то время спин-ожидания д.б. сравнимо со временем выполнения кода, заключенного в эту секцию. Если же "что-то пошло не так" и лок не освобождается, значит поток уснул и продолжать крутить луп не имеет смысла. Нужно наоборот, передать управление системе в надежде на то, что отключение твоего потока ускорит активацию потока, которого ты ждешь
                      Цитата leo @
                      У тебя SetNotify вызывается до создания потока...
                      Слушай, точно. Вот прогон! Видимо, хотел сначала в Execute это сделать, а потом уже по инерции оставил всё как есть <_<

                      Цитата leo @
                      Уже посоветовал простой вариант - использовать FreeOnTerminate:=true и критическую секцию для проверки\установки FResult и посылки PostThreadMessage.
                      Т.е. ты предлагаешь делать FreeOnTerminate := True всегда при вызове Close и не ждать завершения (WaitForSingleObject)?
                        Можно сделать проще:
                        ExpandedWrap disabled
                          procedure TMsgBoxTimeoutNotifyThread.Execute;
                          var Res: TMsgBoxTimeoutResult;
                          begin
                            with MsgBox do with FParams do
                            begin
                              Res := MessageBoxTimeout(hWnd, PChar(Text), PChar(Title), Flags, LangId, Timeout);
                              if InterlockedCompareExchange(Pointer(FResult), Pointer(Res), Pointer(MBTR_DISPLAYING)) = Pointer(MBTR_DISPLAYING) then  // Реузльтат будет другим (не MBTR_DISPLAYING), если окно закрыто методом Close
                                DoNotify(mbtOnClose)
                              else
                              begin
                                FreeOnTerminate := True;
                                FThread := nil
                              end
                            end
                          end;
                           
                          procedure TMsgBoxTimeoutNotify.Close(Res: TMsgBoxTimeoutResult = MBTR_BREAKED);
                          begin
                            Result := True;
                            if Res = MBTR_DISPLAYING then Res := MBTR_BREAKED;
                            if InterlockedExchange(Integer(FResult), Integer(Res)) = Integer(MBTR_DISPLAYING) then
                            begin
                              PostThreadMessage(FThread.ThreadID, WM_QUIT, 0, 0);
                              DoNotify(mbtOnClose)
                            end
                          end;
                        Тогда при принудительном закрытии (Close) всегда будет FreeOnTerminate и FThread будет = nil. В остальных случаях FThread можно использовать.
                        И никакие критические секции не нужны...
                          Цитата Jin X @
                          Можно сделать проще: ...

                          Уф-ф, ну сколько можно путаться в трех соснах?! :wacko:
                          Ну выполнил ты в Close проверку на InterlockedExchange, а винде вдруг приспичило усыпить твой поток сразу после этой проверки. Дальше ясно, что будет? Если нет, то объясняю - ты просыпаешься и как ни в чем не бывало лезешь в FThread.ThreadID (как будто весь мир спал вместе с тобой на критической секции), а метод Execute за это время установил FThread:=nil и возможно удалил объект потока по FreeOnTerminate ...

                          Цитата Jin X @
                          И никакие критические секции не нужны...

                          Нужны. В методе Close проверка состояния FResult и последующие обращения к FThread\ThreadID должны выполняться "атомарно", т.е. так, чтобы метод Execute не смог обнулить FThread и уничтожить объект между проверкой состояния и обращением к переменной FThread. Поэтому тут нужна либо стандартная крит.секция, либо ее аналог на спин-локе в методе Execute с проверкой некого состояния Breaking (которое можно добавить к значениям FResult или же использовать отдельную переменную).
                          Избавиться от крит.секции можно только в случае если 1) не удалять объект объект потока по FreeOnTerminate (или сохранять его ID в отдельной переменной) и 2) предположить, что при завершении потока, его ID не будет использован ОС повторно на коротком интервале времени (хотя официально это не гарантируется).

                          PS: Вообще не понятно, для чего тебе сдалось автоудаление объекта в одном частном случае и не удаление в другом? Почему не использовать однотипный подход - либо FreeOnTerminate всегда true, либо всегда false? Какой-то червяк мелочной псевдо-оптимизации покоя не дает? :)
                            upd: Сообщение исправлено (но код оставил для истории), см. ниже примечания.

                            Цитата leo @
                            Ну выполнил ты в Close проверку на InterlockedExchange, а винде вдруг приспичило усыпить твой поток сразу после этой проверки. Дальше ясно, что будет? Если нет, то объясняю - ты просыпаешься и как ни в чем не бывало лезешь в FThread.ThreadID (как будто весь мир спал вместе с тобой на критической секции), а метод Execute за это время установил FThread:=nil и возможно удалил объект потока по FreeOnTerminate ...
                            Цитата leo @
                            Нужны. В методе Close проверка состояния FResult и последующие обращения к FThread\ThreadID должны выполняться "атомарно", т.е. так, чтобы метод Execute не смог обнулить FThread и уничтожить объект между проверкой состояния и обращением к переменной FThread.
                            Не успел я исправить код до твоего прихода, ну да ладно. В общем, FThread := nil я перенёс в Close:
                            ExpandedWrap disabled
                              procedure TMsgBoxTimeoutNotifyThread.Execute;
                              var Res: TMsgBoxTimeoutResult;
                              begin
                                with MsgBox do with FParams do
                                begin
                                  Res := MessageBoxTimeout(hWnd, PChar(Text), PChar(Title), Flags, LangId, Timeout);
                                  if InterlockedCompareExchange(Pointer(FResult), Pointer(Res), Pointer(MBTR_DISPLAYING)) = Pointer(MBTR_DISPLAYING) then  // Результат будет другим (не MBTR_DISPLAYING), если окно закрыто методом Close
                                    DoNotify(mbtOnClose)
                                  else FreeOnTerminate := True
                                end
                              end;
                               
                              procedure TMsgBoxTimeoutNotify.Close(Res: TMsgBoxTimeoutResult = MBTR_BREAKED);
                              begin
                                if Res = MBTR_DISPLAYING then Res := MBTR_BREAKED;
                                if InterlockedExchange(Integer(FResult), Integer(Res)) = Integer(MBTR_DISPLAYING) then
                                begin
                                  PostThreadMessage(FThread.ThreadID, WM_QUIT, 0, 0);
                                  FThread := nil;
                                  DoNotify(mbtOnClose)
                                end
                              end;
                            Так что, такой проблемы не будет и критическая секция не нужна. (upd: см. ниже примечание и обновление)

                            Цитата leo @
                            предположить, что при завершении потока, его ID не будет использован ОС повторно на коротком интервале времени (хотя официально это не гарантируется).
                            Не гарантируется, но повторюсь, что иначе добрая половина программ была бы потенциально небезопасной. Поэтому этот момент должен быть учтён мелкомягкими (собственно, ты сам об этом и говорил).

                            Цитата leo @
                            PS: Вообще не понятно, для чего тебе сдалось автоудаление объекта в одном частном случае и не удаление в другом? Почему не использовать однотипный подход - либо FreeOnTerminate всегда true, либо всегда false? Какой-то червяк мелочной псевдо-оптимизации покоя не дает?
                            Да, leo, ты прав, это нафиг не нужно. У меня уже столько было различных переделок, что я кое-где запутался и оставил ненужные рудименты. Сейчас всё должно быть ок (только не тут, а см. ниже)! :)

                            Это не червяк! Всё просто. Свойство Thread может использоваться извне для обращения к потоку (например, чтобы найти окно и что-то с ним сделать - поменять текст кнопки). По крайней мере, такую возможность я хочу оставить. Если я сделаю FreeOnTerminate, то такой возможности не будет. Кроме того, использование FThread.ThreadID в Close будет небезопасным (если не делать критические секции).
                            Если я сделаю FreeOnTerminate := False, то после Close я не буду уверен, что объект, присвоенный переменной Thread остановлен, а открытое им окно 100% закрыто. Придётся городить городьбу опять с WaitForSingleObject, критическими секциями и т.п. И возвращать либо True, либо False в зависимости от того, закрылось реально окно или нет. Может, это и неплохо, но большого смысла в этом я уже не вижу.

                            upd: 5 сек... возможно, действительно FreeOnTerminate := True делать уже не смысла (учитывая изменения)... Сейчас чайку попью и, возможно, удалю... :)
                            Сообщение отредактировано: Jin X -
                              Чтобы быть уверенным, что тред остановлен, надо сначала взвести флаг Terminated, а затем вызвать Join().
                              P.S. Да, вызов блокирующий - поэтому надо быть осторожным в написании метода Execute, не уходить в долгие вычисления.
                                Цитата Mr.Delphist @
                                Чтобы быть уверенным, что тред остановлен, надо сначала взвести флаг Terminated, а затем вызвать Join().
                                P.S. Да, вызов блокирующий - поэтому надо быть осторожным в написании метода Execute, не уходить в долгие вычисления.
                                Сам по себе флаг Terminated ничего не гарантирует. Тем более, что в том самом потоке, напомню, выполняется MessageBox.
                                А Join в Delphi7, по крайней мере, нет. Может, WaitFor ты имел в виду?
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0529 ]   [ 16 queries used ]   [ Generated: 3.05.24, 04:48 GMT ]