Deadlock beim Gewinde Schließen
-
23-08-2019 - |
Frage
habe ich eine Klasse, die ein COM-Port und Griffe überlappten Lese- und Schreiboperationen öffnet. Es enthält zwei unabhängige Threads - eine, die liest und eine, die Daten schreibt. Beide nennen OnXXX Verfahren (zB OnRead oder OnWrite) Benachrichtigung über beendet Lese- oder Schreiboperation.
Hier finden Sie ein kurzes Beispiel der Idee, wie die Fäden arbeiten:
TOnWrite = procedure (Text: string);
TWritingThread = class(TThread)
strict private
FOnWrite: TOnWrite;
FWriteQueue: array of string;
FSerialPort: TAsyncSerialPort;
protected
procedure Execute; override;
public
procedure Enqueue(Text: string);
{...}
end;
TAsyncSerialPort = class
private
FCommPort: THandle;
FWritingThread: TWritingThread;
FLock: TCriticalSection;
{...}
public
procedure Open();
procedure Write(Text: string);
procedure Close();
{...}
end;
var
AsyncSerialPort: TAsyncSerialPort;
implementation
{$R *.dfm}
procedure OnWrite(Text: string);
begin
{...}
if {...} then
AsyncSerialPort.Write('something');
{...}
end;
{ TAsyncSerialPort }
procedure TAsyncSerialPort.Close;
begin
FLock.Enter;
try
FWritingThread.Terminate;
if FWritingThread.Suspended then
FWritingThread.Resume;
FWritingThread.WaitFor;
FreeAndNil(FWritingThread);
CloseHandle(FCommPort);
FCommPort := 0;
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Open;
begin
FLock.Enter;
try
{open comm port}
{create writing thread}
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Write(Text: string);
begin
FLock.Enter;
try
{add Text to the FWritingThread's queue}
FWritingThread.Enqueue(Text);
finally
FLock.Leave;
end;
end;
{ TWritingThread }
procedure TWritingThread.Execute;
begin
while not Terminated do
begin
{GetMessage() - wait for a message informing about a new value in the queue}
{pop a value from the queue}
{write the value}
{call OnWrite method}
end;
end;
Wenn Sie an der Close () Verfahren betrachten, werden Sie sehen, dass es in den kritischen Abschnitt eintritt, beendet die Schreib Thread und wartet dann darauf, es zu beenden. Aufgrund der Tatsache, dass der Schreib Thread kann einen neuen Wert enqueue geschrieben werden, wenn es die OnWrite Methode aufruft, wird er versuchen, den gleichen kritischen Abschnitt einzugeben, wenn die Write () Prozedur der TAsyncSerialPort-Klasse aufrufen.
Und hier haben wir eine Sackgasse bekam. Der Faden, der das Schließen () Methode trat in den kritischen Abschnitt genannt, und wartet dann auf das Schreibgewinde geschlossen werden, während zur gleichen Zeit die Thread wartet auf den kritischen Abschnitt befreit werden.
Ich habe für eine recht lange Zeit zu denken und ich habe es nicht geschafft, eine Lösung für dieses Problem zu finden. Die Sache ist, dass ich sicher sein möchte, dass kein Lesen / Schreiben Thread am Leben ist, wenn die Close () Methode gelassen wird, was bedeutet, dass ich nicht nur die Abgebrochene Flagge dieser Threads festlegen kann und verlassen.
Wie kann ich das Problem lösen? Vielleicht sollte ich mein Ansatz ändern asynchron serielle Schnittstelle zu umgehen?
Vielen Dank für Ihre Beratung im Voraus.
Mariusz.
--------- ---------- EDIT
Wie wäre eine solche Lösung?
procedure TAsyncSerialPort.Close;
var
lThread: TThread;
begin
FLock.Enter;
try
lThread := FWritingThread;
if Assigned(lThread) then
begin
lThread.Terminate;
if lThread.Suspended then
lThread.Resume;
FWritingThread := nil;
end;
if FCommPort <> 0 then
begin
CloseHandle(FCommPort);
FCommPort := 0;
end;
finally
FLock.Leave;
end;
if Assigned(lThread) then
begin
lThread.WaitFor;
lThread.Free;
end;
end;
Wenn mein Denken richtig ist, sollte dies das Deadlock-Problem beseitigen. Leider aber schließe ich die COM-Port handhaben, bevor der Schreib Thread geschlossen ist. Das bedeutet, dass, wenn es ruft jede Methode, die den Comm Port Griff als eine seiner Argumente annimmt (zB Schreiben, Lesen, WaitCommEvent) eine Ausnahme in diesem Thread angehoben werden soll. Kann ich sicher sein, dass, wenn ich diese Ausnahme in diesem Thread fangen wird es nicht die Arbeit der gesamten Anwendung auswirken? Diese Frage klingt vielleicht dumm, aber ich denke, dass einige Ausnahmen das Betriebssystem verursachen können die Anwendung zu schließen, die sie verursacht hat, nicht wahr? Muss ich etwa, dass in diesem Fall Sorgen machen?
Lösung
Ja, sollten Sie vielleicht Ihr Konzept überdenken. Asynchrone Operationen zur Verfügung stehen genau den Bedarf an Fäden zu beseitigen. Wenn Sie Threads verwenden, dann synchron (Blockierung) Anrufe verwenden. Wenn Sie asynchrone Operationen verwenden, dann ist alles in einem Thread behandeln -. Nicht unbedingt der Haupt-Thread, aber es macht keinen Sinn IMO das Senden und Empfangen in verschiedenen Threads zu tun
Es gibt natürlich Möglichkeiten, um Ihr Synchronisierungsproblem, aber ich würde eher das Design ändern.
Andere Tipps
können Sie nehmen die Verriegelung aus dem Schließen. Zu der Zeit, sie aus dem WaitFor zurückkehren, ist der Faden Körper bemerkt es beendet wurde, abgeschlossen die letzte Schleife und beendet.
Wenn Sie nicht glücklich fühlen, dies zu tun, dann könnte man Setzen der Sperre bewegen kurz vor dem FreeAndNil. Auf diese Weise kann explizit die Fäden Shutdown Mechanismen funktionieren, bevor Sie die Sperre gelten
(so wird es nicht mit allem, was für die Sperre hat zu konkurrieren)EDIT:
(1) Wenn Sie auch behandeln die Comms schließen es nach der Schleife tun in der Execute oder in dem destructor Thread.
(2) Es tut uns leid, aber Ihre bearbeiteten Lösung ist eine schreckliche Chaos. Terminate und waitfor werden alles tun, was Sie brauchen, vollkommen sicher.
Das Hauptproblem scheint zu sein, dass Sie den gesamten Inhalt der Nähe in einem kritischen Abschnitt platzieren. Ich bin fast sicher (aber Sie werden die Dokumentation überprüfen müssen), dass TThread.Terminate und TThread.WaitFor sicher sind von außerhalb des Abschnitts zu nennen. dass ein Teil außerhalb des kritischen Abschnitt durch Ziehen werden Sie aus der Sackgasse lösen.