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

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


Внимание
Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
Повторная попытка - 60 дней. Последующие попытки бан.
Мат в разделе - бан на три месяца...
Модераторы: jack128, D[u]fa, Shaggy, Rouse_
Страницы: (5) 1 2 [3] 4 5  все  ( Перейти к последнему сообщению )  
> TThread.Synchronize
    Цитата 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 Кбайт, скачиваний: 66)

                          Добавлено
                          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), если переменная не выровнена по границе памяти, атомарным? Т.е. половина переменной находится в одной части памяти, половина - в другой.
                                1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                                0 пользователей:
                                Страницы: (5) 1 2 [3] 4 5  все


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0909 ]   [ 19 queries used ]   [ Generated: 17.05.24, 17:30 GMT ]