스레드를 닫을 때 교착 상태
-
23-08-2019 - |
문제
COM 포트를 열고 겹친 읽기 및 쓰기 작업을 처리하는 클래스를 만들었습니다. 여기에는 두 개의 독립 스레드가 포함되어 있습니다. 하나는 읽기와 데이터를 작성하는 스레드가 포함되어 있습니다. 둘 다 완성 된 읽기 또는 쓰기 작업에 대해 알리는 Onxxx 절차 (예 : Onread 또는 OnWrite)를 호출합니다.
다음은 스레드가 어떻게 작동하는지 아이디어의 짧은 예입니다.
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;
Close () 절차를 보면 중요한 섹션에 들어가서 쓰기 스레드를 종료 한 다음 완료되기를 기다립니다. 쓰기 스레드가 OnWrite 메소드를 호출 할 때 작성해야 할 새 값을 흡수 할 수 있기 때문에 TasyncSerialport 클래스의 write () 절차를 호출 할 때 동일한 중요 섹션을 입력하려고합니다.
그리고 여기 우리는 교착 상태가 있습니다. Close () 메소드라고 불리는 스레드는 임계 섹션에 들어간 다음 쓰기 스레드가 닫히기를 기다리는 반면, 그 스레드가 임계 섹션이 해제 될 때까지 대기합니다.
나는 꽤 오랫동안 생각해 왔으며 그 문제에 대한 해결책을 찾지 못했습니다. 문제는 Close () 메소드가 남을 때 읽기/쓰기 스레드가 살아남지 않기를 원합니다. 즉, 해당 스레드의 종료 된 플래그를 설정하고 떠날 수는 없습니다.
문제를 어떻게 해결할 수 있습니까? 일련의 포트를 비동기 적으로 처리하는 접근 방식을 변경해야할까요?
미리 조언에 감사드립니다.
마리우스.
--------- 편집하다 ----------
그런 해결책은 어떻습니까?
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;
내 생각이 정확하다면 교착 상태 문제를 제거해야합니다. 그러나 불행히도 쓰기 스레드가 닫히기 전에 통신 포트 핸들을 닫습니다. 즉, Comm 포트 핸들을 인수 중 하나로 사용하는 메소드를 호출 할 때 (예 : Write, Read, WaitCommevent) 해당 스레드에서 예외가 제기되어야 함을 의미합니다. 해당 스레드에서 해당 예외를 포착하면 전체 응용 프로그램의 작업에 영향을 미치지 않을 수 있습니까? 이 질문은 어리석게 들릴지 모르지만 일부 예외로 인해 OS가 응용 프로그램을 닫을 수 있다고 생각합니다. 이 경우 그것에 대해 걱정해야합니까?
해결책
예, 당신은 당신의 접근 방식을 재고해야 할 것입니다. 스레드의 필요성을 제거하기 위해 비동기 조작을 정확하게 사용할 수 있습니다. 스레드를 사용하는 경우 동기식 (차단) 호출을 사용하십시오. 비동기 작업을 사용하는 경우 한 스레드에서 모든 것을 처리합니다. 반드시 기본 스레드는 아니지만 다른 스레드에서 보내고 수신하는 것이 IMO가 의미가 없습니다.
물론 동기화 문제에 대한 방법이 있지만 디자인을 변경하고 싶습니다.
다른 팁
닫기에서 잠금을 꺼낼 수 있습니다. 그것이 대기자에게서 돌아올 때까지, 실 본체는 그것이 끝났다는 것을 알았고, 마지막 루프를 완료하고, 끝났다.
이 작업을 수행하는 것이 행복하다고 느끼지 않으면 Freeandnil 직전에 자물쇠를 설정할 수 있습니다. 이것은 잠금을 적용하기 전에 스레드 종료 메커니즘이 작동하게합니다 (따라서 잠금을 위해 아무것도 경쟁 할 필요가 없습니다).
편집하다:
(1) Comms 핸들을 닫으려면 실행 또는 스레드의 파괴자에서 루프 후에 수행하십시오.
(2) 죄송하지만 편집 된 솔루션은 끔찍한 혼란입니다. 종료 및 대기자는 필요한 모든 것을 완벽하게 안전하게 수행합니다.
주요 문제는 중요한 섹션에 가까운 내용을 배치하는 것 같습니다. TTHREAD.THERMINATION 및 TTHREAD.WAITFOR가 섹션 외부에서 전화를 걸기 위해 거의 확실합니다 (그러나 문서를 확인해야합니다). 그 부분을 임계 섹션 밖으로 끌어 당기면 교착 상태가 해결됩니다.