Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > по Mutex: есть вариант, когда не работает


Автор: Ibragim 13.12.2006, 22:46
Вечер добрый. Вот надыбал ситуацию, когда Mutex ведет себя не так как хотелось бы. Точнее даже не Mutex, а WaitForSingleObject

Вот код

Код

  TForm1 = class(TForm)       // На форме одно TMemo и две кнопки
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
<....>

TMyThread = class(TThread)
  procedure Execute; override;
  procedure OnButton2(Sender: TObject);
end;

var
  Form1: TForm1;
  Mutex: THandle;
  t1,t2,t3: TMyThread;

implementation

{$R *.dfm}

procedure TMyThread.Execute;
begin
  WaitForSingleObject(Mutex, INFINITE);
  Form1.Memo1.Lines.Add('Hello world');
  Form1.Button2.OnClick := OnButton2;
end;

procedure TMyThread.OnButton2(Sender: TObject);
begin
  ReleaseMutex(Mutex);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Mutex := CreateMutex(NIL, false, 'MyMutex');
  if Mutex = 0 then
    RaiseLastWin32Error;

  t1 := TMyThread.Create(false);
  t2 := TMyThread.Create(false);
  t3 := TMyThread.Create(false);
end;



По задумке потоки ждут нажатия кнопки оператором, для того чтобы начать вывод. Однако этого не происходит - выводится все три надписи сразу. В описании функции WaitForSingleObject вычитал (DRKB), что она каким-то образом прекращает ожидание, если поток завершился и при этом даже НЕ освободил Mutex. 
Кто посоветует, как это обойти?
Как сделать, чтобы, несмотря на то что выполнение Execute закончилось, остальные потоки все равно ждали ПРЯМОГО УКАЗАНИЯ что можно выводить (ReleaseMutex(Mutex);)?

Заранее спасибо.

Автор: Alexeis 14.12.2006, 00:44
Опять же все обращения к VCL из второго потока можно делать только через Syncronize, все остальные вызовы опасны.

Автор: bems 14.12.2006, 00:57
использовать событие (event) вместо мьютекса

Автор: Ibragim 14.12.2006, 01:50
Цитата(bems @  14.12.2006,  00:57 Найти цитируемый пост)
использовать событие (event) вместо мьютекса


Event мне нельзя или я просто не знаю как его применить к такому случаю. Здесь сотни потоков ожидают одного события - освобождения оператора. Насколько я понимаю, такая ситуация решается именно мьютексами. Если я не прав - поправь плз.

Цитата(alexeis1 @  14.12.2006,  00:44 Найти цитируемый пост)
Опять же все обращения к VCL из второго потока можно делать только через Syncronize, все остальные вызовы опасны


Если это не долго и тебе не сложно, приведи пример плз. Достаточно не программы, а двух ключевых операторов - типа там создаем семафор/мьютекс/т.п, там ждем того-то.

Автор: Ibragim 14.12.2006, 02:05
Даже не так. Вот исходник тестовой проги. Единственое обращение к VCL через Syncronize. Эффект тот же. Чего посоветуете?

Код


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

TMyThread = class(TThread)
  procedure Execute; override;
  procedure Outtext;
end;


var
  Form1: TForm1;
  Mutex: THandle;
  t1,t2,t3: TMyThread;

implementation

{$R *.dfm}

procedure TMyThread.Execute;
begin
  WaitForSingleObject(Mutex, INFINITE);
  Synchronize(Outtext);
end;

procedure TMyThread.Outtext;
begin
  Form1.Memo1.Lines.Add('Ïîòîê ðàáîòàåò');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Mutex := CreateMutex(NIL, false, 'MyMutex');
  if Mutex = 0 then
    RaiseLastWin32Error;

  t1 := TMyThread.Create(false);
  t2 := TMyThread.Create(false);
  t3 := TMyThread.Create(false);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ReleaseMutex(Mutex);
end;

end.

Автор: bems 14.12.2006, 03:26
Цитата(Ibragim @  14.12.2006,  01:50 Найти цитируемый пост)
Здесь сотни потоков ожидают одного события - освобождения оператора. 
И когда освободиться, один из потоков займет мьютекс, а остальные будут ждать уже его.

А если с событием тогда
1. создал событие (CreateEvent).
2. создал потоки, вызывающие WaitForSingleObject с хэндлом события
3. по нажатию кнопки (как в примере) вызываешь SetEvent
4. все ждущие потоки  выполняются дальше

Добавлено @ 03:33 
Цитата(Ibragim @  14.12.2006,  02:05 Найти цитируемый пост)
procedure TForm1.Button2Click(Sender: TObject);
begin
  ReleaseMutex(Mutex);
end;

если эта процедура действительно вызывается как обработчик события (читай - из главного VCL потока) то тут ты пытаешся освободить мьютекс которым не владеешь.

Автор: bems 14.12.2006, 03:53
Цитата(Ibragim @  13.12.2006,  22:46 Найти цитируемый пост)
 Mutex := CreateMutex(NIL, false, 'MyMutex');

вот тут false означает, что у вновь созданного мьютекса нет владельца. Раз так, то один из трех одинаковых потоков (который первым доберется до вызова WaitForSingleObject) моментально его дождется и захватит. Пока он будет рисовать надпись (ну или просто присваивать строку) другие два будут ждать. Когда он выйдет не освободив мьютекса, мьютекс будет щитаться брошенным и его дождется (захватит) слудующий поток
Цитата(Ibragim @  13.12.2006,  22:46 Найти цитируемый пост)
она каким-то образом прекращает ожидание, если поток завершился и при этом даже НЕ освободил Mutex
Если бы этого не происходило оставшиеся два потока зависли бы. Тебе этого надо?

Вобщем нужный эффект будет если ты тут
Цитата(Ibragim @  13.12.2006,  22:46 Найти цитируемый пост)
 Mutex := CreateMutex(NIL, false, 'MyMutex');
заменишь false на true. Тогда владельцем свежего мьютекса будет поток который его создал (первичный поток процесса, VCL - поток). По нажатию кнопки он же его освободит, и по очереди(!!!!!!!) начнут просыпаться те три потока (один сделал дело и завершился - проснулся другой) Будет выглядеть как будто они проснулись одновременно, поскольку работают очень не долго. Но более правильно будет тут использовать эвент, а мьютексы оставить для случая когда ни один из этих трех потоков не должен что-то делать одновременно с другими (MUTual EXclusive access - взаимоисключающий доступ)

Автор: bems 14.12.2006, 04:13
и для синхронизации в пределах одного процесса есть более быстрые способы чем объекты ядра. Смотри критические секции и interlocked-функции

Автор: Ibragim 15.12.2006, 00:55
Большое спасибо bems ! 
Все гуд, разобрался. 
На всякий случай (для тех у кого возникнет такой же вопрос) напишу как сделал.

1. В одном из потоков TThread (менеджер потоков, ссылку на него и так имели ВСЕ потоки) создал
переменную - объект TCriticalSection.
2. Везде, где потокам нужно было для продолжения, завершения работы, показа результатов выполнения и т.д. (у меня около 500 потоков, причем более 20 классов - "видов" потоков) вмешательство оператора - Enter в эту CriticalSection. После - Leave. Работает, даже если Leave вызывается из совершенно другого потока, а тот который сказал Enter давно завершен.

Все, тему можно считать закрытой, спасибо всем особенно bems.

PS Много предупреждений насчет обращений к визуальным компонентам из-под других потоков. Точно проверено (оч. громоздкие задачи, прямо стресс-тест smile )
1. Все нормально без всякой синхронизации работает если писать/читать поля TStringGrid, естественно, не одно поле одновременно.
2. Отлично работает TMemo.Add
3. Отлично работает StatusBar

Автор: bems 15.12.2006, 01:13
Цитата(Ibragim @  15.12.2006,  00:55 Найти цитируемый пост)
PS Много предупреждений насчет обращений к визуальным компонентам из-под других потоков. Точно проверено (оч. громоздкие задачи, прямо стресс-тест  )
1. Все нормально без всякой синхронизации работает если писать/читать поля TStringGrid, естественно, не одно поле одновременно.
2. Отлично работает TMemo.Add
3. Отлично работает StatusBar 
это тебе пока везет. НЕЛЬЗЯ!

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)