На главную Наши проекты:
Журнал   ·   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_
  
> TThread.Synchronize
    Корректно ли делать вот так?
    ExpandedWrap disabled
      TThread.Synchronize(Th, Method);
    При том, что Th может быть = nil.
    Именно поэтому я не стал писать:
    ExpandedWrap disabled
      Th.Synchronize(Method)
      Задача какая стоит?
      Указанная форма Synchronize - классовая статическая, применяется (пока) редко

      Nil использовать можно - главному потоку не всегда требуется знать, кто там синхронизируется.
        Не надо делать Synchronize, вот честно. Лучше делайте PostMessage и избавьтесь от привычки менять UI напрямую из рабочего потока. Ибо Synchronize - это не многопоточность, это ОДНОпоточность. Почитайте, как он работает - смысл тредов убивается напрочь, и владельцы многоядерников высылают автору программы заслуженные лучи поноса.
          Ok, тогда ещё пара вопросов:

          1. Как можно узнать, что TThread (с FreeOnTerminate = True) ещё работает, а не завершился? Если не вешаться на OnTerminate и устанавливать какую-нибудь переменную вне TThread.
          Вообще, можно ли определить, что объект освобождён?

          2. И можно ли сделать так, чтобы переменная TThread обнулялась при завершении работы?

          Добавлено
          И правильно ли я понимаю, что если я прерву поток через TerminateThread (потому как метод Terminate ничего, по сути, не делает), то никакого Free не случится, т.к. этот самый Free (как и вызов метода Execute) происходит в том же самом потоке?
          Да и вообще, насколько это опасно: вызывать TerminateThread (ведь в ThreadProc много чего ещё происходит после Execute)?
            1. Посылать сообщение или выставлять событие. Собственно, OnTerminate решает эту задачу проще всего
            >можно ли определить, что объект освобождён?
            Не нужно этого.

            2. нет

            Спроектируй работу потоков корректно, тогда в абсолютном большинстве случаев не понадобится ничего обнулять и грубо останавливать поток.
              Цитата MBo @
              Спроектируй работу потоков корректно, тогда в абсолютном большинстве случаев не понадобится ничего обнулять и грубо останавливать поток.
              Дело в том, что мне нужно остановить поток, в котором выполняется WinAPI-функция (в частности, MessageBox).
                В дополнительном потоке выполняется работа, требующая блокирующего обращения к пользовательскому интерфейсу??
                Это неправильно.
                  С чего вдруг это неправильно?
                    Цитата Jin X @
                    С чего вдруг это неправильно?

                    Потому что потоку лучше делать расчёты "как заказано".

                    Если у каждого потока будет свой UI, то взаимодействие между ними станет гораздо сложнее. Т.е. это по сути это будут Active Objects в стиле языка SmallTalk со товарищи (Хочешь что-то от потока? Положи ему в очередь сообщений команду. Когда именно он её обработает и ответит - предсказать нельзя, всё взаимодействие будет асинхронное в обе стороны. В результате, тривиальный код выливается в здоровенную state machine, которую трудно читать и ещё труднее - добавлять новый функционал. В ряде случаев синтаксис языка облегчает головную боль, типа кодогенераторов async-await в C# или асинхронных блоков в Objective-C, но кардинального облегчения не будет.)

                    Поэтому в роли "заказчика" пусть выступает основной (aka визуальный) поток, и он уже будет подавать команды рабочему TThread, а тот в роли ведомого - репортить свои достижения обратно через PostMessage или ещё как (почитайте про shared memory, mutex, event и прочие механизмы синхронизации потоков).
                      Цитата Jin X @
                      Да и вообще, насколько это опасно: вызывать TerminateThread (ведь в ThreadProc много чего ещё происходит после Execute)?

                      Вызывать TerminateThread вообще опасно (см. ремарки мсдн), поэтому делать это рекомендуется только в исключительных случаях. После TerminateThread не только ThreadProc не выполняется, а вообще никакой user-mode code (а в XP и ниже даже стэк потока не очищается, что приводит к утечке памяти - по умолчанию 1 Мб адресного пространства). Соотв-но и состояния глобальных переменных, которые мог изменить поток, могут зависнуть в "неконсистентном состоянии". А это могут быть не только залоченные крит.секции, приводящие к очевидному дедлоку, но и неочевидные внутренние состояния системных библиотек, если прерывание потока происходит на вызове функции АПИ (о чем упоминается в тех же ремарках). Простейший пример: если ты собираешься убить поток во время показа MessageBox с hWnd <> 0, то после убийства окно hWnd так и останется заблокированным (disabled) и придется вызывать EnableWindow для его разблокировки. Вопрос - зачем это нужно, если можно решить эту проблему "мирными средствами" (например, вызвать Thread.Terminate и закрыть окно диалога).
                      Цитата Jin X @
                      потому как метод Terminate ничего, по сути, не делает

                      Этот метод делает то, что нужно - указывает потоку, что он должен сам завершиться, чтобы не пришлось прибегать к тяжелой артиллерии и ковровому бомбометанию в виде TerminateThread. Поэтому "корректно спроектированный поток", рассчитанный на длительную обработку (или возможные зависоны) должен периодически и\или в "узловых точках" (например, после показа диалога) проверять флаг Terminated и самостоятельно корректно завершаться при его установке.
                      Сообщение отредактировано: leo -
                        Цитата Jin X @
                        1. Как можно узнать, что TThread (с FreeOnTerminate = True) ещё работает, а не завершился? Если не вешаться на OnTerminate и устанавливать какую-нибудь переменную вне TThread.

                        Можно воспользоваться ReturnValue (менять значения придется самому) или Finished (ставится в True после завершения Execute, при повторном запуске не взводится).
                        С FreeOnTerminate осторожней. Это хорошо для потоков "запустил и забыл", если программа гарантированно дождется завершения, но как только начинаешь рассчитывать на досрочное закрытие приложения, все резко становится намного веселее.
                        Сообщение отредактировано: Fr0sT -
                          Цитата Jin X @
                          Ok, тогда ещё пара вопросов:
                          1. ...
                          2. ...

                          Если используешь FreeOnTerminate = True, то сохранять ссылку на TThread в переменной не имеет никакого смысла, т.к. при асинхронной работе объект может быть разрушен в любой момент, со всеми вытекающими отсюда прелестями, самой лучшей (но маловероятной) из которых является AV. И обнуление переменной тут ничего не даст, т.к. разрушение объекта может произойти сразу после проверки переменной на nil\Assigned.
                          Сообщение отредактировано: leo -
                            Цитата Mr.Delphist @
                            Если у каждого потока будет свой UI, то взаимодействие между ними станет гораздо сложнее..........
                            Зачем так заморачиваться?
                            Речь идёт просто о единичном MessageBox в отдельном потоке, не более того

                            Цитата leo @
                            т.к. разрушение объекта может произойти сразу после проверки переменной на nil\Assigned
                            Это да :)

                            В общем, решил TerminateThread заменить на EnumThreadWindows/SendMessage(WM_CLOSE) :)
                              И все же такая архитектура глубоко порочна, кмк. Тут в рассылке ICS недавно один персонаж похожие штуки проворачивал - показывал мессаджбоксы из обработчиков асинхронных сокетов. Тоже знает толк в извращениях...
                                Fr0sT, в чём порочность? Конструктивно только, без пространных рассуждений о силах вселенной :)

                                Хорошо, пусть обычно основной поток используется для всяких GUI-штук, связки различных компонентов и основных действий. Дополнительные потоки - для вычислений и пр. сопутствующих радостей.
                                Теперь ситуация: я хочу вывести MessageBox "в фоновом режиме" (немодальный то бишь - не будем тут поднимать вопрос "зачем"... если это кому-то непривычно, то не значит, что это не имеет права на жизнь), но такой "встроенной" возможности нет. Т.е. фактически это либо создание окна и работа с ним через VLC (или то же самое, но через WinAPI), либо вывод MessageBox/MessageBoxTimeout в отдельном потоке (ну не пихать же основной код программы в отдельный поток ради того, чтобы MessageBox выводился из основного - это явный бред).

                                Вот здесь я об это уже писал: Найти и закрыть MessageBox
                                Сейчас решил вернуться к этому и закончить дело.
                                  Немодальный Message box http://stackoverflow.com/a/23647706
                                  Цитата

                                  Take a look at the function CreateMessageDialog in unit Vcl.Dialogs. This returns a the standard VCL MessageDialog form which you can manipulate, if necessary, and then show, modal or normally, as you wish.

                                  А порочность вторичного потока с модальностью UI всё та же - надо уметь понимать есть ли у этого потока сейчас модальный диалог на экране и "нажать ему кнопочку", иначе из цикла прокачки сообщений особо и не выйти. А значит - не сделать graceful-завершение (т.е. либо поток остаётся жить вечно и приложение не сможет завершиться, мешая в том числе и нормальной перезагрузке ОС, либо TerminateThread со всеми минусами что описаны выше).

                                  Вообще, при всей своей кажущейся тривиальности, UI-поведение - тема очень и очень многогранная с точки зрения удобства пользователя (user experience, UX). Скажем, ранее я тоже считал, что любой диалог должен быть модальным, всё прочее ересь и неудобство. Но когда я посмотрел на Perforce-клиент (так называемый P4), то был удивлён насколько удобной может быть немодальность диалоговых окон при выполнении тех или иных операций: Сабмитишь? А вот можно зайти в список предыдущих коммитов. Бранчуешь или мёржишь? А вот тебе доступ к общему дереву, к консоли сообщений, к чему угодно. В этом свете стандартные модальные диалоги ОС начинают казаться чугунными кандалами, где можно максимум это лишь погреметь при желании цепью :D
                                    Цитата Jin X @
                                    в чём порочность?

                                    Порочность не в самом MessageBox, а в кривой логике взаимодействия потоков - с одной стороны тебе хочется автоудаления потока через FreeOnTerminate, "не вешаться" на OnTerminate и не "устанавливать какую-нибудь переменную вне TThread", а с другой стороны ты хочешь каким-то странным способом контролировать завершение потока и даже закрывать его MessageBox.
                                    Во-первых, не нужно никакого FreeOnTerminate. Лучше иметь ссылку на объект потока, в которой уже есть масса полезных свойств типа Finished, Handle, ThreadID, а также можно добавить свои, например признак того, что поток находится в состоянии показа MessageBox. Раз тебе все равно приходится контролировать состояние потока, то и удалить его объект не составит труда, когда установлен флаг Finished (и GetExitCodeThread не возвращает STILL_ACTIVE). Если поток находится в состоянии показа MessageBox, то вывести его из этого состояния можно простым PostThreadMessage(ThreadID,WM_QUIT,0,0). Если он может делать что-то еще, то можно использовать штатные средства Terminate\Terminated для корректного досрочного завершения.

                                    PS: Не только вызовы EnumThreadWindows/SendMessage(WM_CLOSE), но и PostThreadMessage(ThreadID,WM_QUIT,0,0) из главного потока по любому выглядят коряво. Лучше в самом классе потока создать метод типа CloseMessageBox (или ExitMessageLoop), в котором будет вызываться PostThreadMessage
                                    Сообщение отредактировано: leo -
                                      Камрады хорошо ответили, добавить особо нечего.
                                      Что касается немодальной мессаги - hWnd = 0 и всё :)
                                        Цитата Mr.Delphist @
                                        Немодальный Message box http://stackoverflow.com/a/23647706
                                        Не хочу я юзать VCL. Можно на WinAPI сделать, конечно. Но так мне видится проще.

                                        Цитата Mr.Delphist @
                                        А порочность вторичного потока с модальностью UI всё та же - надо уметь понимать есть ли у этого потока сейчас модальный диалог на экране и "нажать ему кнопочку", иначе из цикла прокачки сообщений особо и не выйти.
                                        Не вижу тут проблем. EnumThreadWindows - сработала, значит есть (к тому же, у меня есть отдельный флаг для этого). Кидаешь WM_CLOSE (даже если промахнёшься на микросекунду, ничего страшного всё равно не произойдёт).

                                        Цитата leo @
                                        Порочность не в самом MessageBox, а в кривой логике взаимодействия потоков - с одной стороны тебе хочется автоудаления потока через FreeOnTerminate, "не вешаться" на OnTerminate и не "устанавливать какую-нибудь переменную вне TThread", а с другой стороны ты хочешь каким-то странным способом контролировать завершение потока и даже закрывать его MessageBox.
                                        Я наоборот хотел, чтобы FreeOnTerminate устанавливал ThreadObj в nil. А суть этого вопроса вообще в том, чтобы случайно не обратиться к Synchronize уже освобождённого объекта. Но т.к. от Synchronize я отказался, то и вопрос отпал.
                                        А в самом потоке перед завершением устанавливаются флаги (вне TThread) - само собой.

                                        Цитата leo @
                                        Во-первых, не нужно никакого FreeOnTerminate. Лучше иметь ссылку на объект потока, в которой уже есть масса полезных свойств типа Finished, Handle, ThreadID, а также можно добавить свои, например признак того, что поток находится в состоянии показа MessageBox. Раз тебе все равно приходится контролировать состояние потока, то и удалить его объект не составит труда, когда установлен флаг Finished (и GetExitCodeThread не возвращает STILL_ACTIVE). Если поток находится в состоянии показа MessageBox, то вывести его из этого состояния можно простым PostThreadMessage(ThreadID,WM_QUIT,0,0). Если он может делать что-то еще, то можно использовать штатные средства Terminate\Terminated для корректного досрочного завершения.
                                        Постоянно контролировать мне его состояние не надо. Только при определённых условиях (принудительном закрытии MesageBox'а). Поэтому FreeOnTerminate пусть будет. Проблем с обнулением тоже нет, т.к. см. предыдущий коммент.
                                        А вот PostThreadMessage(ThreadID,WM_QUIT,0,0) - это проще, чем EnumThreadWindows :) И про GetExitCodeThread=STILL_ACTIVE может пригодиться ;)

                                        Добавлено
                                        Подскажите ещё... есть ли где-то список всех активных объектов моего класса? Или самому такой только создавать нужно?
                                        Для graceful-завершения как раз...

                                        Добавлено
                                        Цитата Jin X @
                                        Поэтому FreeOnTerminate пусть будет.
                                        Единственный минус - поток может завершиться между проверкой флага, что поток ещё работает и PostThreadMessage.
                                        Но это тоже маловероятно, т.к. здесь у меня всего несколько инструкций, а между завершением TThread.Execute и TThread.Free (по FreeOnTerminate) гораздо больше инструкций.

                                        Добавлено
                                        Мне надо сейчас проработать места, где поток может внезапно завершиться, когда не надо (между проверкой и действием).
                                        Вот думаю TThread.Suspend'ами разбавить (до проверки), т.к. всякие там InterlockedXXXX в данном случае не катят (неудобны).
                                          Цитата Jin X @
                                          Мне надо сейчас проработать места, где поток может внезапно завершиться, когда не надо (между проверкой и действием).
                                          Вот думаю TThread.Suspend'ами разбавить (до проверки)

                                          Госпидя-я... Решить проблему "внезапного завершения" можно только синхронными методами. А SuspendThread - та же опасная асинхронная операция прерывания потока в неопределенном \ непредсказуемом состоянии, как и TerminateThread. Ты вообще в msdn и дельфийскую справку заглядываешь? Зачем все эти кулхацкерские выкрутасы, если есть штатный синхронный способ контролировать завершение потока через OnTerminate? Раз сохранил какие-то ссылки на поток, то повесь ему OnTerminate для обнуления этих ссылок - и всё! Или тебе какая-то сектантская религия не позволяет использовать Synchronize ни под каким соусом?

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

                                          Метод Synchronize предназначен для вызова из самого потока TThread, а не из главного. Как поток может вызвать Synchronize, если он уже завершил Execute и "самоликвидировался"?!
                                          Вызывать Synchronize из главного потока допустимо, но не имеет смысла, т.к. это эквивалентно прямому вызову переданного метода без какой-либо "синхронизации" с потоком, которому принадлежит метод или который передан в качестве параметра в Synchronize(Th, Method).
                                            Цитата leo @
                                            Госпидя-я... Решить проблему "внезапного завершения" можно только синхронными методами. А SuspendThread - та же опасная асинхронная операция прерывания потока в неопределенном \ непредсказуемом состоянии, как и TerminateThread. Ты вообще в msdn и дельфийскую справку заглядываешь? Зачем все эти кулхацкерские выкрутасы, если есть штатный синхронный способ контролировать завершение потока через OnTerminate? Раз сохранил какие-то ссылки на поток, то повесь ему OnTerminate для обнуления этих ссылок - и всё! Или тебе какая-то сектантская религия не позволяет использовать Synchronize ни под каким соусом?
                                            Меня не именно завершение потока интересует (неверно выразился... вернее, _уже_ особо не интересует), а конкретные места в коде потока, где он выполняет что-то (устанавливает значения, вызывает метод и пр). Т.е. мне надо, чтобы это сделал либо доп.поток, либо метод, вызываемый из основного потока, а не оба сразу. А чем Suspend-то опасен?
                                            p.s. А вообще, без а-ля InterlockedXXXX проблематично это сделать. Думал двойной установкой переменных обойтись, но уж не буду дурью маяться (ошибиться легко, а смысла особого нет) :)
                                            p.p.s. Synchronize и OnTerminate (который тоже вызывается через Synchronize) мне не нравятся тем, что прога может быть, например, консольной, и тогда этого OnTerminate мы не дождёмся вообще никогда.

                                            Цитата leo @
                                            Метод Synchronize предназначен для вызова из самого потока TThread, а не из главного. Как поток может вызвать Synchronize, если он уже завершил Execute и "самоликвидировался"?!
                                            Вызывать Synchronize из главного потока допустимо, но не имеет смысла, т.к. это эквивалентно прямому вызову переданного метода без какой-либо "синхронизации" с потоком, которому принадлежит метод или который передан в качестве параметра в Synchronize(Th, Method).
                                            Это понятно. Я хотел вставить этот кусок в метод, который может вызываться как из доп. потока, так и из главного.

                                            Добавлено
                                            Кстати, Synchronize ждёт завершения вызова метода (или просто ставит в очередь)?
                                              Цитата Jin X @
                                              Кстати, Synchronize ждёт завершения вызова метода (или просто ставит в очередь)?

                                              Разумеется, ждет. Иначе это был бы не Synchronize, а что-то другое

                                              Цитата Jin X @
                                              А чем Suspend-то опасен?

                                              Тем, что он прерывает выполнение потока в неопределенном состоянии, например, при захвате им критической секции. А крит.секции, как суслики - ты их не видишь, а они есть - сидят в потайных норах и дельфийского кода (например, в менеджере памяти), и WinAPI.
                                              Вообще, SuspendThread это отладочная функция, которая предназначена для вызова из другого процесса, а не своего собственного.

                                              Цитата Jin X @
                                              Synchronize и OnTerminate (который тоже вызывается через Synchronize) мне не нравятся тем, что прога может быть, например, консольной, и тогда этого OnTerminate мы не дождёмся вообще никогда.

                                              Час от часу не легче. А вдруг немцы? (С)
                                              Synchronize в доп.потоке должна работать в паре с CheckSynchronize в главном потоке. В GUI-приложении вызов CheckSynchronize встроен в цикл обработки Application.Run (точнее в Application.Idle). В консольной проге встроенного механизма нет, поэтому нужно либо вызывать CheckSynchronize периодически, либо там, где нужно по логике работы проги, либо использовать другие методы синхронизации потоков.

                                              Цитата Jin X @
                                              Постоянно контролировать мне его состояние не надо. Только при определённых условиях (принудительном закрытии MesageBox'а).

                                              Какая разница - постоянно или однократно? Как ты узнаешь, что нужно закрыть MessageBox? Тебе сам поток как-то об этом сигнализирует (или разгневаннаый юзер) или ты по своей инициативе проверяешь наличие этого бокса? Если ты по любому хоть раз, как-то проверяешь состояние потока, то почему-бы его и не удалять из памяти при этой проверке? А если поток завершится раньше, и объект TThread повисит какое-то время памяти, то что в этом страшного?
                                                Цитата leo @
                                                Тем, что он прерывает выполнение потока в неопределенном состоянии, например, при захвате им критической секции. А крит.секции, как суслики - ты их не видишь, а они есть - сидят в потайных норах и дельфийского кода (например, в менеджере памяти), и WinAPI.
                                                Вообще, SuspendThread это отладочная функция, которая предназначена для вызова из другого процесса, а не своего собственного.
                                                Так, прервал Suspend выполнение потока в неопределённом состоянии, пусть даже при захвате критической секции... иии???
                                                Что плохого-то в этом? Если я вскоре вызову ResumeThread ?

                                                Цитата leo @
                                                Synchronize в доп.потоке должна работать в паре с CheckSynchronize в главном потоке. В GUI-приложении вызов CheckSynchronize встроен в цикл обработки Application.Run (точнее в Application.Idle). В консольной проге встроенного механизма нет, поэтому нужно либо вызывать CheckSynchronize периодически, либо там, где нужно по логике работы проги, либо использовать другие методы синхронизации потоков.
                                                Ну начинается... Т.е. я, делая модуль, реализующий какой-то функционал в фоновом режиме, должен написать: "Вызывайте CheckSynchronize почаще, пожалуйста, а то могут возникать глюки"? Зачем, если я могу всё это сделать без Synchronize? :)

                                                Цитата leo @
                                                Какая разница - постоянно или однократно? Как ты узнаешь, что нужно закрыть MessageBox? Тебе сам поток как-то об этом сигнализирует (или разгневаннаый юзер) или ты по своей инициативе проверяешь наличие этого бокса?
                                                А зачем мне его закрывать? Есть MessageBoxTimeout, который сам закроет окно после определённого таймаута. И есть метод Close, который можно вызвать из программы и закрыть это окно.

                                                Цитата leo @
                                                Если ты по любому хоть раз, как-то проверяешь состояние потока, то почему-бы его и не удалять из памяти при этой проверке? А если поток завершится раньше, и объект TThread повисит какое-то время памяти, то что в этом страшного?
                                                А какой в этом смысл? Зачем мне его удалять из памяти при "этой" проверке? И зачем потоку висеть в памяти какое-то время? Если можно просто сделать FreeOnTerminate := True, а перед завершением потока установить флаг во внешней переменной? Тем более, что устанавливать флаг всё равно нужно, т.к. при всё той же "этой" проверке мне надо как-то узнать, что поток ещё не удалён.
                                                Для чего это нужно? Если можно сделать проще :)

                                                Добавлено
                                                leo, разговор уже ушёл далеко от первоначальной темы :)
                                                И мне даже кажется, что мы уже немного говорим не о том.
                                                Я как доделаю эту штуку (руки до неё всё никак не дойдут), выложу и можно будет подискутировать где какие косяки :)
                                                Может, кому-то модуль и пригодится...

                                                Добавлено
                                                Собственно, я сейчас уже всё доделал, осталось только оттестить и отладить. Но это уже не сегодня :)
                                                  Цитата Jin X @
                                                  Что плохого-то в этом? Если я вскоре вызову ResumeThread ?

                                                  в том, что если ты засуспензил поток, в в момент, когда менеджер памяти залочен в крит секции, то ты ты сам не можешь использовать менеждер памяти. а менеджер памяти используется явно и не явно везде. ты разве что сложить пару чисел можешь без него, не меньше.
                                                    В данном случае я собираюсь (собирался... уже по-человечески, через InterlockedComparExchgane сделал) Suspend'ить доп. поток для проверки переменных, т.е. чтобы "сложить" (сравнить) пару чисел. Мне менеджер памяти не нужен был.
                                                      Если уж так надо суспендить поток, то делай через виндовые средства (эвенты, семафоры и т.д.) и WaitFor*. Ну или по-топорному, while ThreadPaused do Sleep(100). Это хотя бы безопасно.
                                                      А вообще чем дальше, тем меньше я понимаю, чего ты хочешь достичь. Неверная архитектура задачи видится мне.
                                                        Цитата Jin X @
                                                        leo, разговор уже ушёл далеко от первоначальной темы.
                                                        И мне даже кажется, что мы уже немного говорим не о том.

                                                        Во-первых, первоначальная тема (также как и упомянутая "Найти и закрыть MessageBox"), все больше напоминает сюжет "Саги об X, Y и Z", когда вместо вопроса по сути решаемой задачи, задаются и обсуждаются вопросы, которые не имеют к данной задаче никакого отношения или решают ее, мягко говоря, не лучшим, а то и извращенным способом. А о сути и нюансах самой задачи мы узнаем по крупицам в ходе обсуждения (например, тема начиналась с вопроса о Synchronize, а затем вдруг всплыло консольное приложение, в котором Synchronize без.доп телодвижений работать не может).
                                                        Мы говорим как раз "о том", но ты упорно придерживаешься выбранного ранее пути решения и приводишь в его оправдание совершенно нелогичные доводы.
                                                        Тебе сразу сказали, что FreeOnTermiate имеет смысл использовать только в случаях, когда 1) тебя совершенно не интересует "судьба" потока - как говорится, запустил и забыл, 2) используется стандартный способ контроля завершения потока через событие OnTerminate (или через свои примочки типа Post\SendMessage). В остальных случаях, когда возникает желание или необходимость контролировать завершение потока не через OnTerminate, задавать FreeOnTerminate = true, не имеет смысла, т.к. это не упрощает, а лишь усложняет логику взаимодействия. И все твои примочки и блуждания в трех соснах (от Synchronize до Terminate\SuspendTrhead и InterlockedXXX) это лишь подтверждают.
                                                        Ты в предыдущем посте несколько раз повторил "а зачем?", "проще" и т.п. Нет, не проще. Встречный вопрос: зачем "установить флаг во внешней переменной" "... всё равно нужно", если всё, что нужно уже есть в самой переменной TThread? Разве использовать несколько доп.переменных и самому их устанавливать проще, чем использовать готовый объект, в котором все устанавливается автоматически? Зачем доверять потоку удалять свой объект по FreeOnTerminate? Чтобы потом чесать репу - а удалился ли он уже, или находится в состоянии удаления, и не удалится ли на следующей строчке после проверки какого-то там флага, и городить ради этого огород с SuspendTread, шаманить с Interlocked-функциями и т.п.? Зачем все это? Не проще ли самому удалить объект потока, когда он завершился и больше не нужен (хоть в FormDestroy или в завершающем finally консольной проги)? Или ты считаешь, что любую мало-мальскую память нужно освобождать сразу после ее использования? Типа создал строку, передал ее куда-то - непременно вызови S:='', чтобы она не висела в памяти в ожидании автоочистки?

                                                        Добавлено
                                                        Цитата Jin X @
                                                        А если этот менеджер памяти мне приспичило использовать в тот момент, когда доп. поток залочит его в критической секции (без Suspend'а), то я смогу его использовать? Он будет ждать когда доп. поток его освободит что ли?

                                                        Разумеется. На то она и крит.секция, чтобы разрешать выполнять некоторый кусок кода только одному потоку. Поэтому, если ты остановишь поток в крит.секции менеджера памяти, то любое неявное выделение\освобождение памяти между Suspend и Resume (например, при операциях со строками) приведет к зависону.
                                                        Ты конечно можешь этого избежать, всё "оттестить и отладить", но по любому SuspendTread - это специальная функция, использовать которую в обычных\нормальных приложениях не рекомендуется. Поэтому, юзать ее ты можешь только "на собственный страх и риск для домашнего использования".
                                                        Сообщение отредактировано: leo -
                                                          leo, всё просто. Обсуждается тема unit'а по созданию немодального MessageBox, который висит в фоне и закрывается либо по таймауту, либо юзером, либо методом Close. Он может работать в консольном приложении, а не только в GUI. Вот и всё.

                                                          Цитата leo @
                                                          Ты в предыдущем посте несколько раз повторил "а зачем?", "проще" и т.п. Нет, не проще. Встречный вопрос: зачем "установить флаг во внешней переменной" "... всё равно нужно", если всё, что нужно уже есть в самой переменной TThread? Разве использовать несколько доп.переменных и самому их устанавливать проще, чем использовать готовый объект, в котором все устанавливается автоматически? Зачем доверять потоку удалять свой объект по FreeOnTerminate? Чтобы потом чесать репу - а удалился ли он уже, или находится в состоянии удаления, и не удалится ли на следующей строчке после проверки какого-то там флага, и городить ради этого огород с SuspendTread, шаманить с Interlocked-функциями и т.п.? Зачем все это? Не проще ли самому удалить объект потока, когда он завершился и больше не нужен (хоть в FormDestroy или в завершающем finally консольной проги)?
                                                          Я, честно говоря, вообще не понимаю, как ты предлагаешь организовать весь процесс. Смотри, логика проста до безобразия.
                                                          Есть метод Show, который создаёт поток (с FreeOnTerminate) и устанавливает переменную (в классе) Status в значение Displaying - таким образом и сам класс и использующий его код будет знать, что окно отображается. Поток создаёт сообщение MessageBoxTimeout, после завершения которого (и непосредственно перед выходом) устанавливает Status в другое значение (Timedout, Button или Breaked). Таким образом я (класс) знаю, есть ли работающий поток, а программа (вызывающая класс) знает, отображается ли сообщение в данный момент. Метод Close (принудительное закрытие) проверяет - Displaying ли сейчас окно? Если да, то посылает ему PostThreadMessage(WM_QUIT). Это всё.
                                                          Вопрос: зачем мне нужно сделать FreeOnTerminate := False и самому его освобождать? Единственным доводом в этом пользу может служить разве что "вдруг поток освободиться в Close между проверкой Displaying и PostThreadMessage, и тогда я обращусь к несуществующему Thread.Handle (при вызове PostThreadMessage)?" Но и тут есть простое решение: в этом случае поток может сделать сам себе FreeOnTerminate := False, а Close сделать Thread.Free. Это единственный момент, когда это может быть оправдано, на мой взгляд. Если ты другого мнения, пожалуйста, расскажи. Потому что пока я не понимаю смысла делать иначе :). Да, можно делать Free именно в Show, но чем это лучше?

                                                          Цитата leo @
                                                          Или ты считаешь, что любую мало-мальскую память нужно освобождать сразу после ее использования? Типа создал строку, передал ее куда-то - непременно вызови S:='', чтобы она не висела в памяти в ожидании автоочистки?
                                                          Вот это тут вообще ни причём. Другое дело, что если есть выбор (и это не усложняет код и не делает его менее надёжным), лучше освободить, не так ли?

                                                          Добавлено
                                                          Цитата leo @
                                                          В остальных случаях, когда возникает желание или необходимость контролировать завершение потока не через OnTerminate, задавать FreeOnTerminate = true, не имеет смысла, т.к. это не упрощает, а лишь усложняет логику взаимодействия. И все твои примочки и блуждания в трех соснах (от Synchronize до Terminate\SuspendTrhead и InterlockedXXX) это лишь подтверждают.
                                                          Блуждание произошло из-за того, что я вместо Interlocked зачем-то решил использовать двойные флаги и/или Suspend, чем сейчас и сам удивляюсь.
                                                          А Synchronize нужен был для оповещения основной программы о том, что "произошёл запуск" или "окно закрылось". Типа OnXXXX и иже с ними. Сначала хотел сделать 2 варианта: прямой вызов и через Synchronize, потом решил сделать только вызов напрямую, а программист уже сам решит: нужен ему Synchronize или нет и вызовет его.
                                                            Вот такой вопрос у меня ещё: если я выполняю какую-то операцию (например, PostThreadMessage) с потоком, который только что завершил свою работу, ничего страшного, как я понимаю, не произойдёт.
                                                            Но! Может ли возникнуть ситуация, что создастся поток с таким же Id и я закрою что-то другое?
                                                            Каким образом эти Id генерируются?
                                                              Цитата Jin X @
                                                              Обсуждается тема unit'а по созданию немодального MessageBox, который висит в фоне и закрывается либо по таймауту, либо юзером, либо методом Close. Он может работать в консольном приложении, а не только в GUI. Вот и всё.

                                                              Тьфу ты блин, сразу сказать не судьба была?
                                                              https://github.com/Fr0sT-Brutal/Delphi_DlgCountdown
                                                                Цитата Jin X @
                                                                Единственным доводом в этом пользу может служить разве что "вдруг поток освободиться в Close между проверкой Displaying и PostThreadMessage, и тогда я обращусь к несуществующему Thread.Handle (при вызове PostThreadMessage)?" Но и тут есть простое решение: в этом случае поток может сделать сам себе FreeOnTerminate := False, а Close сделать Thread.Free.

                                                                Простое решение - это использование критической секции (или ее самописного аналога). В рабочем потоке заключаем в крит.секцию изменение статуса Displaying после показа MessageBox, а в методе Close - проверку статуса и посылку сообщения PostThreadMessage. В этом случае все "вдруг" исключаются и можно использовать FreeOnTerminate:=true.

                                                                Цитата Jin X @
                                                                Может ли возникнуть ситуация, что создастся поток с таким же Id и я закрою что-то другое?
                                                                Каким образом эти Id генерируются?

                                                                Подробности мне не известны, но думаю, что на коротких интервалах переназначение ID невозможно. Ведь по любому между получением ID процесса или потока и его открытием проходит какое-то время, за которое этот процесс или поток может успеть закрыться. Поэтому в ОС должен использоваться некий механизм назначения ID, исключающий их повтор на коротких интервалах времени.
                                                                PS: Однако, если есть возможность перестраховаться (например, с использованием крит.секции), то лучше это сделать, "кабы чего не вышло" ;)
                                                                  Цитата leo @
                                                                  Простое решение - это использование критической секции (или ее самописного аналога). В рабочем потоке заключаем в крит.секцию изменение статуса Displaying после показа MessageBox, а в методе Close - проверку статуса и посылку сообщения PostThreadMessage. В этом случае все "вдруг" исключаются и можно использовать FreeOnTerminate:=true.
                                                                  К Diaplaying всё равно никто доступа не имеет, поэтому можно и не заключать :)
                                                                  Но я всё равно убрал FreeOnTerminate, оказалось, что всё-таки так удобнее делать некоторые вещи :D. Не получается "забыть" после того, как запустил.
                                                                  Так что спасибо за настойчивость ;)

                                                                  Цитата leo @
                                                                  PS: Однако, если есть возможность перестраховаться (например, с использованием крит.секции), то лучше это сделать, "кабы чего не вышло"
                                                                  У меня есть такой блок:
                                                                  ExpandedWrap disabled
                                                                    function TMsgBoxTimeoutNotify.Close(Res: TMsgBoxTimeoutResult = MBTR_BREAKED): Boolean;
                                                                    begin
                                                                      Result := True;
                                                                      if Res = MBTR_DISPLAYING then Res := MBTR_BREAKED;
                                                                      if InterlockedExchange(Integer(FResult), Integer(Res)) = Integer(MBTR_DISPLAYING) then
                                                                        PostThreadMessage(FThread.ThreadID, WM_QUIT, 0, 0);
                                                                      if (FThread <> nil) and (FThread.Handle <> 0) then  // Даже если метод Close вызван уже после установки результата потоком, всё равно...
                                                                      begin
                                                                        Result := (WaitForSingleObject(FThread.Handle, FCloseTimeout) <> WAIT_TIMEOUT);  // ...ждём реального закрытия окна (обработки PostMessage).
                                                                        DoNotify(mbtOnClose);
                                                                        if not Result then
                                                                        begin
                                                                          FThread.FreeOnTerminate := True;  // Если не дождались, отправляем его в свободное плавание.
                                                                          FThread := nil
                                                                        end
                                                                      end
                                                                    end;
                                                                  Два выделенных красным цветом участка теоретически могут попасть на выход из потока (хотя крайне маловероятно, т.к. здесь код короче, чем выход из Execute и завершение потока).
                                                                  Благо TThread не обнуляет Handle и ThreadID при завершении работы потока.
                                                                  Но это уже придирки, конечно, т.к. я полагаю, что иначе добрая половина существующих программ, работающих с WaitForSingleObject и иже с ним, были бы "условно ненадёжными" :)

                                                                  Добавлено
                                                                  А вот так сделан вывод сообщения:
                                                                  ExpandedWrap disabled
                                                                    function TMsgBoxTimeoutNotify.ShowEx(hWnd: THandle; const Text, Title: String; Flags, Timeout: DWord; Ntf: TMsgBoxTimeoutNotifyRec; const Id: String = ''; LangId: Word = 0): Boolean;
                                                                    begin
                                                                      Result := ReadyToShow(Flags);
                                                                      if Result then
                                                                      begin
                                                                        FreeAndNil(FThread);
                                                                        FResult := MBTR_DISPLAYING;
                                                                        FParams.hWnd := hWnd;
                                                                        FParams.Text := Text;
                                                                        FParams.Title := Title;
                                                                        FParams.Flags := Flags and not MBTF_FLAGS_MASK;
                                                                        FParams.Timeout := Timeout;
                                                                        FParams.LangId := LangId;
                                                                        FId := Id;
                                                                        if FId = '' then FId := GenerateId;
                                                                        SetNotify(Ntf);  // От греха подальше пусть будет SetNotify, а не прямое присвоение :)
                                                                        FThread := TMsgBoxTimeoutNotifyThread.Create(True);
                                                                        with TMsgBoxTimeoutNotifyThread(FThread) do
                                                                        begin
                                                                          MsgBox := Self;
                                                                          DoNotify(mbtOnShow);
                                                                          Resume
                                                                        end
                                                                      end
                                                                    end;
                                                                     
                                                                    procedure TMsgBoxTimeoutNotify.SetNotify(Ntf: TMsgBoxTimeoutNotifyRec);
                                                                    begin
                                                                      while InterlockedExchange(NotifyDenyToChange, 1) = 1 do Sleep(0);
                                                                      FNotify := Ntf;
                                                                      NotifyDenyToChange := 0;
                                                                    end;
                                                                     
                                                                    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 not FreeOnTerminate then  // Поток не в свободном плавании (см. метод Close)?
                                                                          if InterlockedCompareExchange(Pointer(FResult), Pointer(Res), Pointer(MBTR_DISPLAYING)) = Pointer(MBTR_DISPLAYING) then  // Реузльтат будет другим (не MBTR_DISPLAYING), если окно закрыто методом Close
                                                                            DoNotify(mbtOnClose)
                                                                      end
                                                                    end;
                                                                     
                                                                    procedure TMsgBoxTimeout.BeforeDestruction;
                                                                    begin
                                                                      Close;
                                                                      FThread.Free
                                                                    end;

                                                                  Весь код пришлю позже, отладить всё надо :)
                                                                  Сообщение отредактировано: Jin X -
                                                                    С простыми переменными < размера регистра нет смысла юзать interlocked, чтобы проверить их значение. Чтение-запись в них и так атомарна.
                                                                      Fr0sT, все атомарные (locked) операции и так проводятся над данными размером с регистр (а для 64 и 128 бит можно юзать MMX и SSE).
                                                                      Внутри процессора производятся такие же операции, как и в обычной программе. Меняем EAX и память [M32]: Temp=EAX; EAX=[M32]; [M32]=Temp.
                                                                      И вот где-то по пути в работу может вмешаться другое ядро, тогда получится:
                                                                      ExpandedWrap disabled
                                                                        Temp1=EAX1   // Ядро1
                                                                        Temp2=EAX2   // Ядро2
                                                                        EAX1=[M32]   // Ядро1
                                                                        EAX2=[M32]   // Ядро2
                                                                        [M32]=Temp1  // Ядро1
                                                                        [M32]=Temp2  // Ядро2
                                                                      Что получим?
                                                                      На примере:
                                                                      ExpandedWrap disabled
                                                                        Если:
                                                                        EAX1=10
                                                                        EAX2=20
                                                                        [M32]=30
                                                                         
                                                                        Тогда:
                                                                        Temp1=EAX1   // Temp1=10
                                                                        Temp2=EAX2   // Temp2=20
                                                                        EAX1=[M32]   // EAX1=30
                                                                        EAX2=[M32]   // EAX2=30
                                                                        [M32]=Temp1  // [M32]=10
                                                                        [M32]=Temp2  // [M32]=20
                                                                         
                                                                        В итоге получили:
                                                                        EAX1=30
                                                                        EAX2=30
                                                                        [M32]=20
                                                                      Как видим, значение 10 пропало (с InterlockedCompareExchange всё будет ещё плачевнее...)
                                                                      А если это делать по очереди (с locked'ами), то будет:
                                                                      ExpandedWrap disabled
                                                                        Ядро1:
                                                                        Temp1=EAX1   // Temp1=10
                                                                        EAX1=[M32]   // EAX1=30
                                                                        [M32]=Temp1  // [M32]=10
                                                                         
                                                                        Ядро2:
                                                                        Temp2=EAX2   // Temp2=20
                                                                        EAX2=[M32]   // EAX2=10
                                                                        [M32]=Temp2  // [M32]=20
                                                                         
                                                                        В итоге получили:
                                                                        EAX1=30
                                                                        EAX2=10
                                                                        [M32]=20
                                                                      Всё в порядке.

                                                                      Аналогично с увеличением на 1: EAX=[M32]; EAX++; [M32]=EAX.
                                                                      ExpandedWrap disabled
                                                                        Пусть:
                                                                        [M32]=10
                                                                         
                                                                        Тогда:
                                                                        EAX1=[M32]  // EAX1=10
                                                                        EAX2=[M32]  // EAX2=10
                                                                        EAX1++      // EAX1=11
                                                                        EAX2++      // EAX2=11
                                                                        [M32]=EAX1  // [M32]=11
                                                                        [M32]=EAX2  // [M32]=11
                                                                      Ну, а что должно быть, я думаю, и так понятно...
                                                                      (на самом деле там используется xadd, который немного по-другому работает, но для иллюстрации такой пример сгодится).

                                                                      Да и просто, пойдём от простой логики: если бы это было не нужно, не было бы функций InterlockedCompare, InterlockedIncrement и пр.
                                                                        Я не говорю про обмен - на уровне Паскаля это уже не одна операция. Я говорю о чтении или записи. Тогда (чтение тривиально, рассмотрим запись):

                                                                        ExpandedWrap disabled
                                                                              [M_src1]=10
                                                                              [M_src2]=20
                                                                              [M32]=30
                                                                              
                                                                              Тогда:
                                                                              EAX1=[M_src1]   // EAX1=30
                                                                              EAX2=[M_src2]   // EAX2=20
                                                                              [M32]=EAX1  // [M32]=10
                                                                              [M32]=EAX2  // [M32]=20
                                                                              
                                                                              В итоге получили:
                                                                              EAX1=10
                                                                              EAX2=20
                                                                              [M32]=30


                                                                        Добавлено
                                                                        Цитата Jin X @
                                                                        все атомарные (locked) операции и так проводятся над данными размером с регистр (а для 64 и 128 бит можно юзать MMX и SSE)

                                                                        Это к чему? Я знаю.

                                                                        Добавлено
                                                                        Цитата Jin X @
                                                                        Да и просто, пойдём от простой логики: если бы это было не нужно, не было бы функций InterlockedCompare, InterlockedIncrement и пр.

                                                                        С InterlockedIncrement очевидно (это опять же 2 операции), а вот InterlockedCompare я не очень понимаю зачем нужно. Разве что сразу получить прежнее значение.
                                                                        Сообщение отредактировано: Fr0sT -
                                                                          Цитата Fr0sT @
                                                                          С InterlockedIncrement очевидно (это опять же 2 операции)
                                                                          С точки зрения ASM-кода это как раз 1 операция INC [M32].
                                                                          Цитата Fr0sT @
                                                                          Я не говорю про обмен - на уровне Паскаля это уже не одна операция.
                                                                          С точки зрения паскаля это тоже 1 операция, он так же генерит XCHG [M32],EAX.
                                                                          Цитата Fr0sT @
                                                                          InterlockedCompare я не очень понимаю зачем нужно. Разве что сразу получить прежнее значение.
                                                                          Да, как минимум для этого. Как уже сказал, это тоже одна операция XCHG [M32],EAX.
                                                                          InterlockedCompareExchange = CMPXCHG [M32],EAX.
                                                                          Там везде 1 операция. Вопрос не в количестве инструкций ассемблера, а в кол-ве операций ВНУТРИ процессора.
                                                                          Просто чтение и просто запись (A := 10; он же MOV [M32],EAX) - согласен, смысла нет (и то, это просто логическое предположение, х/з как там внутри устроено).
                                                                          Но где идёт изменение хранящегося внутри значения, обмен (со сравнением или без) - там да.

                                                                          Добавлено
                                                                          Цитата Fr0sT @
                                                                          Это к чему? Я знаю.
                                                                          Это к тому, что ты пишешь:
                                                                          Цитата Fr0sT @
                                                                          С простыми переменными < размера регистра нет смысла юзать interlocked, чтобы проверить их значение. Чтение-запись в них и так атомарна.


                                                                          Добавлено
                                                                          Я даже больше скажу: ДВЕ операции (не внутри процессора, а в количестве ассемблерных инструкций) ты залочить никак не сможешь. Ну разве что сделать spinlock, но это уже псевдо-лок.
                                                                            Цитата Jin X @
                                                                            С точки зрения ASM-кода это как раз 1 операция INC [M32].

                                                                            Если с возвратом старого значения - две. Если старое не нужно - то одна, и тогда и InterlockedIncrement не особо нужен.
                                                                            Цитата Jin X @
                                                                            С точки зрения паскаля это тоже 1 операция, он так же генерит XCHG [M32],EAX.

                                                                            Попробуй сгенерить кодом на Паскале такой asm ;) я имею в виду, без всяких функций. Компилятор не настолько умный, к сожалению. А стало быть, это целых три операции.
                                                                            Цитата Jin X @
                                                                            Вопрос не в количестве инструкций ассемблера, а в кол-ве операций ВНУТРИ процессора.

                                                                            Я не большой знаток устройства процов, но мне кажется, что инструкция, хотя бы снаружи - вне зависимости от того, как реализовано унутрях - атомарна. Иначе любому менеджеру потоков/процессов пришлось бы лочить каждую инструкцию, чтобы не дай бог не запустить другой поток, пока выполняется инструкция прежнего.
                                                                            Цитата Jin X @
                                                                            Это к тому, что ты пишешь:

                                                                            Все равно не понял, зачем эта фраза, ну да ладно.
                                                                              Цитата Fr0sT @
                                                                              Если с возвратом старого значения - две. Если старое не нужно - то одна, и тогда и InterlockedIncrement не особо нужен.
                                                                              Зачем что-то утверждать, если не знаешь точно? <_<
                                                                              InterlockedIncrement выполняет 1 операцию (инструкцию CPU): xadd !!! Которая прибавляет некоторое значение, одновременно считывая старое.
                                                                              Смотри картинку...

                                                                              Прикреплённый файлПрикреплённый файл2016_09_16_15_30_29_Unit1.pas.png (13,35 Кбайт, скачиваний: 422)

                                                                              Добавлено
                                                                              Повторюсь:
                                                                              Цитата Jin X @
                                                                              ДВЕ операции (не внутри процессора, а в количестве ассемблерных инструкций) ты залочить никак не сможешь. Ну разве что сделать spinlock, но это уже псевдо-лок.
                                                                              Все Interlocked-функции работают с 1 операцией! За исключением inline-функций C++, которые организуют spinlock типа
                                                                              ExpandedWrap disabled
                                                                                FORCEINLINE
                                                                                LONGLONG
                                                                                _InlineInterlockedExchange64 (
                                                                                    _Inout_ _Interlocked_operand_ LONGLONG volatile *Target,
                                                                                    _In_ LONGLONG Value
                                                                                    )
                                                                                {
                                                                                    LONGLONG Old;
                                                                                 
                                                                                    do {
                                                                                        Old = *Target;
                                                                                    } while (InterlockedCompareExchange64(Target,
                                                                                                                          Value,
                                                                                                                          Old) != Old);
                                                                                 
                                                                                    return Old;
                                                                                }
                                                                                 
                                                                                #define InterlockedExchange64 _InlineInterlockedExchange64
                                                                                 
                                                                                FORCEINLINE
                                                                                LONGLONG
                                                                                _InlineInterlockedExchangeAdd64 (
                                                                                    _Inout_ _Interlocked_operand_ LONGLONG volatile *Addend,
                                                                                    _In_ LONGLONG Value
                                                                                    )
                                                                                {
                                                                                    LONGLONG Old;
                                                                                 
                                                                                    do {
                                                                                        Old = *Addend;
                                                                                    } while (InterlockedCompareExchange64(Addend,
                                                                                                                          Old + Value,
                                                                                                                          Old) != Old);
                                                                                 
                                                                                    return Old;
                                                                                }
                                                                                 
                                                                                #define InterlockedExchangeAdd64 _InlineInterlockedExchangeAdd64
                                                                              и т.д. Но это 64-битные функции, которые необходимо использовать в 32-битной среде (такая организация spinlock'ов необходимо потому, что для обмена регистров с 64-битными данными есть только лишь одна инструкция процессора cmpxchg8b... ну, может, ещё в MMX что-то есть, не знаю, но это как-то не используется (предположительно чтобы не удалить чего лишнего, тем более, что регистры MMX имеют единое пространство данных с регистрами FPU)).
                                                                              Сообщение отредактировано: Jin X -
                                                                                Бррр. Мы о разном говорим. Я - о том, что без interlocked функций определенные действия на Паскале будут транслироваться в 2 и более инструкций (обмен, сравнение-и-присвоение, увеличение-и-возврат-старого и т.д.). Которые, разумеется, небезопасны с т.з. мультитредовости. Однако если операция на Паскале транслируется в одну инструкцию (напр., Inc без получения старого значения), то необходимости в interlocked нет.
                                                                                  Цитата Fr0sT @
                                                                                  Бррр. Мы о разном говорим. Я - о том, что без interlocked функций определенные действия на Паскале будут транслироваться в 2 и более инструкций (обмен, сравнение-и-присвоение, увеличение-и-возврат-старого и т.д.). Которые, разумеется, небезопасны с т.з. мультитредовости. Однако если операция на Паскале транслируется в одну инструкцию (напр., Inc без получения старого значения), то необходимости в interlocked нет.
                                                                                  А, вон ты о чём!
                                                                                  Но тут ты тоже не прав :D. Посмотри ещё раз скриншот - там перед xadd стоит lock. Не просто так. Как я уже говорил, второе ядро может вклиниться в работу во время (внутренних!!!) вычислений первого и будет как в моём сообщении #34, где оба одновременно пытаются изменить значение, а оно меняется только на 1 вместо 2.
                                                                                  Я накатал "по бырому" демку сего процесса. Вот основной код (см. TMyThread.Execute):
                                                                                  ExpandedWrap disabled
                                                                                    unit Main;
                                                                                     
                                                                                    interface
                                                                                     
                                                                                    uses
                                                                                      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
                                                                                      Dialogs, StdCtrls, ExtCtrls, ComCtrls;
                                                                                     
                                                                                    type
                                                                                      TForm1 = class(TForm)
                                                                                        Button1: TButton;
                                                                                        Edit1: TEdit;
                                                                                        Label1: TLabel;
                                                                                        CheckBox1: TCheckBox;
                                                                                        TrackBar1: TTrackBar;
                                                                                        Label2: TLabel;
                                                                                        Button2: TButton;
                                                                                        Edit2: TEdit;
                                                                                        procedure Button1Click(Sender: TObject);
                                                                                        procedure FormClose(Sender: TObject; var Action: TCloseAction);
                                                                                        procedure CheckBox1Click(Sender: TObject);
                                                                                        procedure TrackBar1Change(Sender: TObject);
                                                                                        procedure Button2Click(Sender: TObject);
                                                                                      private
                                                                                        { Private declarations }
                                                                                      public
                                                                                        { Public declarations }
                                                                                      end;
                                                                                     
                                                                                      TMyThread = class(TThread)
                                                                                        N: Integer;
                                                                                        procedure Execute; override;
                                                                                      end;
                                                                                     
                                                                                    var
                                                                                      Form1: TForm1;
                                                                                      A, B: TMyThread;
                                                                                      X: Integer;
                                                                                      D: Integer = 0;
                                                                                      Run: Boolean = False;
                                                                                      UseLock: Boolean = False;
                                                                                     
                                                                                    implementation
                                                                                     
                                                                                    {$R *.dfm}
                                                                                     
                                                                                    procedure TMyThread.Execute;
                                                                                    begin
                                                                                      N := 0;
                                                                                      repeat
                                                                                        asm
                                                                                          mov eax,Self
                                                                                          cmp UseLock,0
                                                                                          je @NoLock
                                                                                          lock inc [X]
                                                                                          inc [eax+N]
                                                                                          jmp @End
                                                                                        @NoLock:
                                                                                          inc [X]
                                                                                          inc [eax+N]
                                                                                        @End:
                                                                                        end;
                                                                                        Sleep(D)
                                                                                      until Terminated
                                                                                    end;
                                                                                     
                                                                                    procedure TForm1.Button1Click(Sender: TObject);
                                                                                    begin
                                                                                      if Run then
                                                                                      begin
                                                                                        A.Terminate;
                                                                                        B.Terminate;
                                                                                        Exit
                                                                                      end;
                                                                                      Run := True;
                                                                                      X := 0;
                                                                                      A := TMyThread.Create(False);
                                                                                      B := TMyThread.Create(False);
                                                                                      repeat
                                                                                        Edit1.Text := IntToStr((A.N+B.N)-X);
                                                                                        Application.ProcessMessages;
                                                                                        Sleep(1)
                                                                                      until A.Terminated;
                                                                                      A.Free;
                                                                                      B.Free;
                                                                                      Run := False
                                                                                    end;
                                                                                     
                                                                                    procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
                                                                                    begin
                                                                                      if Run then Button1.Click
                                                                                    end;
                                                                                     
                                                                                    procedure TForm1.CheckBox1Click(Sender: TObject);
                                                                                    begin
                                                                                      UseLock := CheckBox1.Checked
                                                                                    end;
                                                                                     
                                                                                    procedure TForm1.TrackBar1Change(Sender: TObject);
                                                                                    begin
                                                                                      D := TrackBar1.Position;
                                                                                      Edit2.Text := IntToStr(D)
                                                                                    end;
                                                                                     
                                                                                    procedure TForm1.Button2Click(Sender: TObject);
                                                                                    begin
                                                                                      X := 0;
                                                                                      A.N := 0;
                                                                                      B.N := 0
                                                                                    end;
                                                                                     
                                                                                    end.

                                                                                  Суть в том, что два потока увеличивают внешнюю переменную X на 1 (с lock'ом и без lock'а) ассемблером (т.е. это однозначно единая операция) и внутреннюю переменную на 1 (каждый свою A.N и B.N).
                                                                                  И в процессе их работы мы постоянно выводим на экран "ошибку" (A.N+B.N) - X. Если галочки "LOCK" нет, это число постоянно увеличивается. Если есть, то рост "ошибки" останавливается. Угадай почему? :)
                                                                                  Если двигать ползунок Sleep (который будет увеличивать задержку между изменением значений в миллисекундах) вправо, то скорость роста "ошибки" при выключенном "LOCK" замедляется, что говорит о том, что в нормальных условиях таких совпадений не возникает (на больших значениях ждать приходится очень долго), но хороший программист всё же должен учитывать такую возможность (даже если она 1 на триллион или меньше, потому что, как говорят, "на грех и грабли стреляют") :whistle:
                                                                                  При Sleep=0 и галочке "LOCK" показатель дёргается - это нормально, т.к. чтение значений A.N, B.N и X (а также увеличение X и N в каждом из потоком) происходит не атомарно.

                                                                                  Прикреплённый файлПрикреплённый файлThreadLockDemo.zip (206,75 Кбайт, скачиваний: 62)
                                                                                  Сообщение отредактировано: Jin X -
                                                                                    Цитата Fr0sT @
                                                                                    Я не большой знаток устройства процов, но мне кажется, что инструкция, хотя бы снаружи - вне зависимости от того, как реализовано унутрях - атомарна. Иначе любому менеджеру потоков/процессов пришлось бы лочить каждую инструкцию, чтобы не дай бог не запустить другой поток, пока выполняется инструкция прежнего.
                                                                                    Цитата Fr0sT @
                                                                                    Однако если операция на Паскале транслируется в одну инструкцию (напр., Inc без получения старого значения), то необходимости в interlocked нет.

                                                                                    Любая инструкция процессора (кроме строковых операций с rep) атомарна в плане прерывания\исключения, т.е. либо она выполняется полностью, либо отменяется полностью. Поэтому в однопроцессорной системе инструкции, выполняющие чтение и запись в память, также являются атомарными и в плане чтения\записи (если не рассматривать специальные экзотические случаи, когда память м.б. изменена не самим процессором, а "внешними агентами" - видеокартой, контроллером DMA и т.п.). Но в многопроцессорных (многоядерных) системах x86 эти инструкции в общем случае не являются атомарными в плане чтения\записи, т.к. между (микро)операциями чтения и записи, память может быть изменена другим логическим процессором. Например, если два потока на разных логических процессорах делают Inc одной и той же переменной, то она в итоге может измениться как на 2, так и на 1 (если оба потока одновременно прочитают одно и тоже значение). Если же выполняется lock inc, то гарантируется, что между чтением и записью ни один другой поток не сможет не только изменить значение переменной, но и даже прочитать ее ("неконсистентное" значение). Поэтому два lock inc в разных потоках гарантированно увеличивают значение переменной на 2, а не на 1-2, как при простом Inc.
                                                                                      Цитата Fr0sT @
                                                                                      Если с возвратом старого значения - две. Если старое не нужно - то одна, и тогда и InterlockedIncrement не особо нужен.
                                                                                      Interlocked-функции, зашитые в DLL, сделаны не для паскаля/Delphi. И даже не для C++, т.к. проще сделать в C++ и в Delphi такую функцию (с xadd), чем пихать её в DLL. А по поводу возврата старого значения, так почему бы не сделать так:
                                                                                      ExpandedWrap disabled
                                                                                        X := A;  // Получаем старое значение
                                                                                        A := A + 1;  // Увеличиваем на 1
                                                                                      По твоим рассуждениям та же атомарность получилась бы... ну и что, что между этими двумя (не знаю зачем 3 ты предлагаешь делать, но не важно) строчками кто-то влез бы, всё равно задача выполнена: старое значение получили и увеличили его на 1. Т.к. даже при атомарной операции мы не можем контролировать то, что происходит после неё. Ан-нет, так не канает :)

                                                                                      Цитата leo @
                                                                                      Но в многопроцессорных (многоядерных) системах x86 эти инструкции в общем случае не являются атомарными в плане чтения\записи, т.к. между (микро)операциями чтения и записи, память может быть изменена другим логическим процессором. Например, если два потока на разных логических процессорах делают Inc одной и той же переменной, то она в итоге может измениться как на 2, так и на 1 (если оба потока одновременно прочитают одно и тоже значение).
                                                                                      Добавлю, что под микрооперацией подразумевается внутреняя операция в процессоре (ядре), т.е. Temp = [M32]; Temp++; [M32] = Temp.
                                                                                      Собственно, реальное поведение демонстрирует приведённая выше программа.

                                                                                      А вот ещё одна. Использование CMPXCHG с LOCK'ом и без для SpinLock'ов (организации критических секций - участков кода, доступ к которому должен иметь только 1 поток).
                                                                                      Прикреплённый файлПрикреплённый файлThreadSpinLockDemo.zip (210,96 Кбайт, скачиваний: 63)

                                                                                      Добавлено
                                                                                      p.s. Справедливости ради хочу сказать, что есть 1 инструкция, которые всегда делает Bus-Lock (т.е. её не нужно предварять префиксом LOCK) - это XCHG (не CMPXCHP, не CMPXCHG8B и пр, а обычная XCHG).
                                                                                        Вот из документации Intel:
                                                                                        Цитата
                                                                                        5 Examples
                                                                                        Two examples of generic spin-wait loops are given here.

                                                                                        5.1 A Sample Spin-wait Lock
                                                                                        ExpandedWrap disabled
                                                                                          get_lock: mov eax, 1
                                                                                           xchg eax, A ; Try to get lock
                                                                                           cmp eax, 0 ; Test if successful
                                                                                           jne spin_loop
                                                                                          critical_section:
                                                                                           <critical section code>
                                                                                           mov A, 0 ; Release lock
                                                                                           jmp continue
                                                                                          spin_loop:
                                                                                           pause ; Short delay
                                                                                           cmp 0, A ; Check if lock is free
                                                                                           jne spin_loop
                                                                                           jmp get_lock
                                                                                          continue:

                                                                                        5.2 Another Spin-wait Sample
                                                                                        ExpandedWrap disabled
                                                                                          // Come here if we didn’t get the lock on the first try.
                                                                                          for (;;)
                                                                                          {
                                                                                            for (int i=0; i < SPIN_COUNT; i++)
                                                                                            {
                                                                                              if ( (i & SPIN_MASK) == 0
                                                                                                && m_dwLock == UNLOCKED
                                                                                                && InterlockedExchange( &m_dwLock, LOCKED )== UNLOCKED)
                                                                                              return;
                                                                                              #ifdef _X86_
                                                                                               _mm_pause();
                                                                                              #endif
                                                                                            }
                                                                                            SleepForSleepCount( cSleeps++ );
                                                                                          }
                                                                                        Немного странная конструкция, особенно на ассемблере, её можно упростить.
                                                                                        Инструкция pause, как пишут, повышает производительность процессора за счёт оптимизация работы процессора с памятью и уменьшения мощности процессора (потребления энергии).
                                                                                        Однако на загрузку процессора это не влияет. (Прикол, что эту инструкцию можно запустить даже на 8086, т.к. это, по сути rep nop :))

                                                                                        Кстати, leo, посоветоваться с тобой хочу.
                                                                                        Вот старт SpinLock'а:
                                                                                        ExpandedWrap disabled
                                                                                          procedure StartSpinLock(var X: Integer);
                                                                                          begin
                                                                                            while InterlockedExchange(X, 1) <> 0 do
                                                                                            begin
                                                                                              asm pause end;
                                                                                          {$IFNDEF NoSleepSpinloops}
                                                                                              Sleep(1)
                                                                                          {$ENDIF}
                                                                                            end
                                                                                          end;
                                                                                        Я добавил Sleep(1), чтобы снизить нагрузку на процессор, потому что иначе цикл грузит его на максимум (Sleep(0) тоже не помогает).
                                                                                        Так вообще корректно делать?
                                                                                          Идеально :D 8-)
                                                                                          ExpandedWrap disabled
                                                                                            procedure StartSpinLock(var Key: Integer);
                                                                                            var i: Integer;
                                                                                            begin
                                                                                              i := 0;
                                                                                              while InterlockedExchange(Key, 1) <> 0 do
                                                                                              begin
                                                                                                asm pause end;
                                                                                                if HiWord(i) = 0 then Inc(i)
                                                                                                else Sleep(1)
                                                                                              end
                                                                                            end;
                                                                                             
                                                                                            procedure FinishSpinLock(var Locker: Integer);
                                                                                            begin
                                                                                              Key := 0
                                                                                            end;
                                                                                            У меня вот тут вопрос возник: будет ли обычное 32-битное присвоение (mov [X],eax), если переменная не выровнена по границе памяти, атомарным? Т.е. половина переменной находится в одной части памяти, половина - в другой.
                                                                                              Если адрес переменной не выравнен на ее размер, то атомарность чтения\записи не гарантируется. Хотя в 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 ты имел в виду?
                                                                                                                            Частично выкладываю что получилось...

                                                                                                                            Описание:
                                                                                                                            ExpandedWrap disabled
                                                                                                                                  // Ждать закрытия окна сообщения (нажатия кнопки или срабатывания таймаута).
                                                                                                                                  // Во время ожидания метод постоянно вызывает Sleep(1), а также функцию/метод Idle, если он(а) задан(а) каждые Period миллисекунд (первый вызов осуществляется сразу).
                                                                                                                                  // При значении Period = INFINITE вызов Idle выполняется только 1 раз. Если функция/метод вернёт False, цикл ожидания прервётся, а метод вернёт значение MBTR_WAITBREAKED.
                                                                                                                                  // В остальных случаях возвращает результат, т.е. свойство GetResult (аналогично функциям MessageBoxTimeout, MsgBoxTimeout).
                                                                                                                                  function Wait(Idle: TMsgBoxTimeoutIdleFunc = nil; Period: DWord = 0): TMsgBoxTimeoutResult; overload;
                                                                                                                                  function Wait(Idle: TMsgBoxTimeoutIdleMethod; Period: DWord = 0): TMsgBoxTimeoutResult; overload;
                                                                                                                               
                                                                                                                                  // Принудительно закрыть окно сообщения, установив значение результата Res (при указании значения MBTR_DISPLAYING оно будет заменено на MBTR_BREAKED).
                                                                                                                                  // Результат устанавливается вне зависимости от состояния окна (открыто или закрыто). Если окно сообщения уже закрыто, ничего страшного не произойдёт.
                                                                                                                                  // ВНИМАНИЕ: Данный метод лишь посылает потоку команду закрыть окно, но не ждёт, когда это реально произойдёт, поэтому нет гарантии, что после завершения метода окно уже будет
                                                                                                                                  // закрыто (хотя это должно произойти очень быстро). В любом случае можно не опасаться вызывать метод Show/ShowEx сразу после метода Close, т.к. окно будет считаться уже закрытым.
                                                                                                                                  // Данный метод не вызывает метод Free!
                                                                                                                                  procedure Close(Res: TMsgBoxTimeoutResult = MBTR_BREAKED);
                                                                                                                               
                                                                                                                                  // Перед уничтожением потока вызвать метод Close и освободить поток.
                                                                                                                                  destructor Destroy; override;
                                                                                                                               
                                                                                                                                  // Используемый в работе объект потока. Предоставляется на страх и риск программиста (например, для доступа к окну сообщения)!
                                                                                                                                  // Может быть равен nil, если поток ещё ни разу не запускался. В остальных случаях объект не освобождается, а свойство не обнуляется, даже если окно уже закрыто (в т.ч. методом Close).
                                                                                                                                  // ВНИМАНИЕ: Уничтожение потока в обход метода TMsgBoxTimeout.Close (например, через TerminateThread) или освобождение объекта Thread (Thread.Free) приведёт к некорректной дальнейшей работе и исключениям!
                                                                                                                                  property Thread: TThread read FThread;

                                                                                                                            Реализация:
                                                                                                                            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)
                                                                                                                                end
                                                                                                                              end;
                                                                                                                               
                                                                                                                              function TMsgBoxTimeout.ReadyToShow(Flags: DWord): Boolean;
                                                                                                                              begin
                                                                                                                                Result := True;
                                                                                                                                if FResult = MBTR_DISPLAYING then
                                                                                                                                  case Flags and MBTF_REACTION_MASK of
                                                                                                                                    MBTF_WAIT: Wait;
                                                                                                                                    MBTF_FAIL: Result := False
                                                                                                                                    else Close
                                                                                                                                  end
                                                                                                                              end;
                                                                                                                               
                                                                                                                              function TMsgBoxTimeoutNotify.ShowEx(hWnd: THandle; const Text, Title: String; Flags, Timeout: DWord; Ntf: TMsgBoxTimeoutNotifyRec; const Id: String = ''; LangId: Word = 0): Boolean;
                                                                                                                              begin
                                                                                                                                Result := ReadyToShow(Flags);
                                                                                                                                if Result then
                                                                                                                                begin
                                                                                                                                  FreeAndNil(FThread);  // Даже если ShowEx вызван сразу после Close, а поток ещё не успел завершить работу, деструктор TThread подождёт его завершения
                                                                                                                                  FParams.hWnd := hWnd;
                                                                                                                                  FParams.Text := Text;
                                                                                                                                  FParams.Title := Title;
                                                                                                                                  FParams.Flags := Flags and not MBTF_FLAGS_MASK;
                                                                                                                                  FParams.Timeout := Timeout;
                                                                                                                                  FParams.LangId := LangId;
                                                                                                                                  FId := Id;
                                                                                                                                  if FId = '' then FId := GenerateId;
                                                                                                                                  FNotify := Ntf;
                                                                                                                                  FThread := TMsgBoxTimeoutNotifyThread.Create(True);
                                                                                                                                  FResult := MBTR_DISPLAYING;
                                                                                                                                  with TMsgBoxTimeoutNotifyThread(FThread) do
                                                                                                                                  begin
                                                                                                                                    MsgBox := Self;
                                                                                                                                    DoNotify(mbtOnShow);
                                                                                                                                    Resume
                                                                                                                                  end
                                                                                                                                end
                                                                                                                              end;
                                                                                                                               
                                                                                                                              function TMsgBoxTimeout.Wait(Idle: TMsgBoxTimeoutIdleFunc = nil; Period: DWord = 0): TMsgBoxTimeoutResult;
                                                                                                                              var T1, T2: Int64;
                                                                                                                              begin
                                                                                                                                T1 := 0;
                                                                                                                                while FResult = MBTR_DISPLAYING do
                                                                                                                                begin
                                                                                                                                  if @Idle <> nil then
                                                                                                                                  begin
                                                                                                                                    T2 := GetTickCount;
                                                                                                                                    if (T1 = 0) or ((T2-T1 >= Period) and (Period <> INFINITE)) then
                                                                                                                                    begin
                                                                                                                                      if not Idle(Self) then
                                                                                                                                      begin
                                                                                                                                        Result := MBTR_WAITBREAKED;
                                                                                                                                        Exit
                                                                                                                                      end;
                                                                                                                                      T1 := T2
                                                                                                                                    end
                                                                                                                                  end;
                                                                                                                                  Sleep(1)
                                                                                                                                end;
                                                                                                                                Result := FResult
                                                                                                                              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);
                                                                                                                                  DoNotify(mbtOnClose)
                                                                                                                                end
                                                                                                                              end;
                                                                                                                               
                                                                                                                              destructor TMsgBoxTimeout.Destroy;
                                                                                                                              begin
                                                                                                                                Close;
                                                                                                                                FThread.Free;  // Если окно уже было закрыто до вызова Close
                                                                                                                                inherited
                                                                                                                              end;
                                                                                                                            0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                                                                                            0 пользователей:


                                                                                                                            Рейтинг@Mail.ru
                                                                                                                            [ Script execution time: 0,1699 ]   [ 21 queries used ]   [ Generated: 19.04.24, 02:23 GMT ]