(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Синхронизация потоков в Delphi

Источник: delphiplus
Вячеслав Минияров

Атор: Вячеслав Минияров, Delphi Plus

Одно время пришлось плотно работать с потоками, причём с десяток методов требовали переключения в основной поток. Самое неприятное, что методы все с параметрами, причем разными. Наверняка я был не первый, кто объявлял массив вариантов, в который записывал параметры вызова, затем Synchronize(Caller), а в Caller - вызов той же функции с параметрами из массива. В конце концов мне это надоело: ведь все параметры вызываемого метода уже лежат в стеке! Стал думать, и вот что получилось:

Сначала - как это выглядит в коде:

// создаём и запускаем поток
procedure TForm1.Button1Click(Sender: TObject);
begin
  Th:= TVThread.Create;
end;

procedure TVThread.Execute;
var s: string;
begin
  // Form1.Test должна выполняться в контексте главного потока
  s:= Form1.Test(123, 'Abcd');
  Form1.Hint:= s;
end;

// параметры - любого типа, результат - тоже
function TForm1.Test(p1: integer; p2: string): string;
begin
  if Th.SFork then exit; // собственно, СИНХРОНИЗАЦИЯ
  ......                 // код для выполнения в основном потоке
  ......
end;

Симпатично? Мне нравится :)

А теперь о грустном:

  • метод (или простая функция), вызывающая SFork, не может быть типа register: только pascal, cdecl или stdcall (почему - будет ясно ниже);
  • если метод не статический (виртуальный или динамический) или вызван не напрямую (через переменную процедурного типа, например, или это Event), то вместо простого и красивого SFork придётся вызывать Fork:
    function TForm1.Test(p1: integer; p2: string): string;
    begin
      if Th.Fork(@TForm1.Test) then exit;
    ....
    
    с передачей ему адреса синхронизуемого метода.

Всё от того, что адрес точки входа в вызывающий метод можно определить только при прямой адресации и никак - при косвенной. :(

Естественно, Th.Fork(Pointer) можно использовать и для статических методов.

Реализация:

Метод Fork (SFork) сначала проверяет, в каком потоке его вызвали. Если это - главный поток, то ничего не делает, возвращает FALSE. Иначе вычисляет по содержимому стека начало и размер блока параметров вызвавшей его процедуры и запоминает эти данные в своих полях. Затем через стандартный Synchronize вызывается метод _Fork. _Fork, выполняясь уже в контексте главного потока, резервирует в стеке место, копирует в него блок параметров из стека заторможенного в это время потока и вызывает снова тот же метод (TForm1.Test в нашем примере). Этот метод теперь будет выполнен до конца, т.к. теперь Fork вернёт false.

По поводу безопасности кода: т.к. оба раза выполняется код входа в метод и код выхода из него, то все строки и пр. подобные типы, переданные как параметры, нормально увеличивают/уменьшают счётчики ссылок и нормально же отрабатывают автоматически добавляемые компилятором блоки try..finally..except.

  TVThread = class(TThread)
  private
    FRC: dword;      // адрес синхронизуемого метода
    FStart: dword;   // начало блока параметров этого метода
    FCount: dword;
  protected
    procedure _Fork;
    procedure ErrorCall; dynamic;
    procedure DoTerminate; override;
    procedure Execute; override;  // длина этого блока
  public
    function Fork(Func: Pointer): boolean;
    function SFork: boolean;
    constructor Create;  // только для статических методов!
  end;

procedure TVThread._Fork;
var savsi: dword;
    savdi: dword;
asm
    mov   savsi, ESI        // сохранить регистры
    mov   savdi, EDI
    mov   ESI, [EAX].FStart // src ptr
    mov   ECX, [EAX].FCount
    mov   EDX, [EAX].FRC
    sub   ESP, ECX
    mov   EDI, ESP          // dst ptr
    rep movsb               // копировать блок параметров
    mov   ESI, savsi        // восстановить регистры
    mov   EDI, savdi
    call  EDX               // вызвать синхронизуемый метод
end;

function TVThread.Fork(Func: Pointer): boolean;
begin
  asm
    mov  result, false
    push EAX                // save @Self
    call GetCurrentThreadId
    pop  ECX                // ECX = @Self
    cmp  EAX, MainThreadID
    jz   @@nothing          // если осн. поток - выход
    mov  result, true
    mov  EAX, Func
    mov  [ECX].FRC, EAX     // запомнить адрес метода
    mov  EAX, [EBP]         // EAX = EBP метода Func
// EBP перед вызовом Func и адрес возврата из Func нам не нужны
    lea  EDX, [EAX+8]
    mov  [ECX].FStart, EDX  // начало блока параметров
    mov  EAX, [EAX]         // ESP перед вызовом Func = конец блока
    sub  EAX, EDX           // длина блока
    mov  [ECX].FCount, EAX  // запомнить длину
    mov  AL, true           // продублировать result в AL
@@nothing:
  end;        // Synchronize лучше вызывать вне ASM блока, т.к.
              // компилятор тогда сам (и правильно) создаст
              // try..finally обрамление в операторах begin и end
  if result then
    Synchronize(_Fork);
end;

function TVThread.SFork: boolean;
var error: boolean;
begin
  asm
    mov  result, false
    push EAX                // save @Self
    call GetCurrentThreadId
    pop  ECX                // ECX = @Self
    cmp  EAX, MainThreadID
    jz   @@nothing          // если осн. поток - выход
    mov  result, true
    mov  error, true
    mov  EAX, [EBP]         // EAX = EBP вызвавшей процедуры
    mov  EAX, [EAX+4]       // EAX = адрес возврата из неё
  // вызов статического метода на ассемблере выглядит так:
  // первый байт - E8 (call), далее четыре байта - смещение
  // относительно адреса следующей инструкции.
  // адрес возврата указывает именно на эту следующую инструкцию
    cmp  byte ptr [EAX-5],0E8h  // проверить способ вызова
    jne  @@nothing              // Ошибка! Не прямой вызов.
    mov  error, false
    add  EAX, [EAX-4]       // EAX = caller entry point
    mov  [ECX].FRC, EAX     // сохранить адрес вызвавшей процедуры
    mov  EAX, [EBP]         // далее всё идентично Fork
    lea  EDX, [EAX+8]
    mov  [ECX].FStart, EDX
    mov  EAX, [EAX]
    sub  EAX, EDX
    mov  [ECX].FCount, EAX
    mov  AL, true
@@nothing:
  end;
  if not result then exit;
  if error then begin
    ErrorCall;     // в наследнике можно обругаться по своему
    // если Exception всё же не был вызван в ErrorCall..
    raise Exception.Create(
          'TVThread.SFork: Error Calling conventions!');
  end else
    Synchronize(_Fork);
end;

procedure TVThread.ErrorCall;
begin
end;

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 14.09.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Delphi Professional Named User
Enterprise Connectors (1 Year term)
Enterprise Connectors (1 Year term)
Контур.Доступ
VMware Workstation 14 Pro for Linux and Windows, ESD
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Компьютерный дизайн - Все графические редакторы
СУБД Oracle "с нуля"
Все о PHP и даже больше
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100