현재 두 개의 스레드를 번갈아 실행하고 서로 기다리게하는 최선의 방법을 찾고 있습니다.Delphi에서 두 스레드를 서로 동기화하는 가장 좋은 방법
(*) 낮은 CPU를 가진 것은
나는 내가 발견 한 문제를 보여주기 위해 몇 가지 데모 응용 프로그램에 함께 넣어 지금까지 세 가지 방법을 발견 비용 동안 빠른되는 최상의 조합.일반적인 대기/펄스 패턴을 따르는 TMonitor를 사용하면 모든 잠금 때문에 성능이 좋지 않습니다 (SamplingProfiler는 이러한 기능에서 대부분의 시간을 소모합니다). 나는 Windows 이벤트 (SyncObjs.TEvent)를 사용하여 같은 것을 시도했지만 비슷한 (즉, 불량) 이벤트를 수행했다.
TThread.Yield를 호출하는 대기 루프를 사용하면 성능은 좋지만 분명히 CPU주기는 열중합니다. 전환 작업이 매우 빨라지더라도 문제가되지는 않지만 스레드가 실제로 기다리고있을 때 아프다 (데모에서 볼 수 있음).
TSpinWait을 사용하면 위의 세 가지 중에서 가장 좋지는 않지만 스위치가 매우 빠르게 작동하는 경우에만 수행됩니다. TSpinWait의 작동 방식 때문에 성능이 저하되는 데 오랜 시간이 걸립니다.
멀티 스레딩이 내 강점 중 하나가 아니기 때문에 두 가지 시나리오 (빠른 스위치와 느린 스위치) 모두에서 좋은 성능을 얻기 위해 이러한 방법 또는 일부 완전히 다른 접근 방법이 있는지 궁금합니다.
program PingPongThreads;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Classes,
Diagnostics,
SyncObjs,
SysUtils;
type
TPingPongThread = class(TThread)
private
fCount: Integer;
protected
procedure Execute; override;
procedure Pong; virtual;
public
procedure Ping; virtual;
property Count: Integer read fCount;
end;
TPingPongThreadClass = class of TPingPongThread;
TMonitorThread = class(TPingPongThread)
protected
procedure Pong; override;
procedure TerminatedSet; override;
public
procedure Ping; override;
end;
TYieldThread = class(TPingPongThread)
private
fState: Integer;
protected
procedure Pong; override;
public
procedure Ping; override;
end;
TSpinWaitThread = class(TPingPongThread)
private
fState: Integer;
protected
procedure Pong; override;
public
procedure Ping; override;
end;
{ TPingPongThread }
procedure TPingPongThread.Execute;
begin
while not Terminated do
Pong;
end;
procedure TPingPongThread.Ping;
begin
TInterlocked.Increment(fCount);
end;
procedure TPingPongThread.Pong;
begin
TInterlocked.Increment(fCount);
end;
{ TMonitorThread }
procedure TMonitorThread.Ping;
begin
inherited;
TMonitor.Enter(Self);
try
if Suspended then
Start
else
TMonitor.Pulse(Self);
TMonitor.Wait(Self, INFINITE);
finally
TMonitor.Exit(Self);
end;
end;
procedure TMonitorThread.Pong;
begin
inherited;
TMonitor.Enter(Self);
try
TMonitor.Pulse(Self);
if not Terminated then
TMonitor.Wait(Self, INFINITE);
finally
TMonitor.Exit(Self);
end;
end;
procedure TMonitorThread.TerminatedSet;
begin
TMonitor.Enter(Self);
try
TMonitor.Pulse(Self);
finally
TMonitor.Exit(Self);
end;
end;
{ TYieldThread }
procedure TYieldThread.Ping;
begin
inherited;
if Suspended then
Start
else
fState := 3;
while TInterlocked.CompareExchange(fState, 2, 1) <> 1 do
TThread.Yield;
end;
procedure TYieldThread.Pong;
begin
inherited;
fState := 1;
while TInterlocked.CompareExchange(fState, 0, 3) <> 3 do
if Terminated then
Abort
else
TThread.Yield;
end;
{ TSpinWaitThread }
procedure TSpinWaitThread.Ping;
var
w: TSpinWait;
begin
inherited;
if Suspended then
Start
else
fState := 3;
w.Reset;
while TInterlocked.CompareExchange(fState, 2, 1) <> 1 do
w.SpinCycle;
end;
procedure TSpinWaitThread.Pong;
var
w: TSpinWait;
begin
inherited;
fState := 1;
w.Reset;
while TInterlocked.CompareExchange(fState, 0, 3) <> 3 do
if Terminated then
Abort
else
w.SpinCycle;
end;
procedure TestPingPongThread(threadClass: TPingPongThreadClass; quickSwitch: Boolean);
const
MAXCOUNT = 10000;
var
t: TPingPongThread;
i: Integer;
sw: TStopwatch;
w: TSpinWait;
begin
t := threadClass.Create(True);
try
for i := 1 to MAXCOUNT do
begin
t.Ping;
if not quickSwitch then
begin
// simulate some work
w.Reset;
while w.Count < 20 do
w.SpinCycle;
end;
if i = 1 then
begin
if not quickSwitch then
begin
Writeln('Check CPU usage. Press <Enter> to continue');
Readln;
end;
sw := TStopwatch.StartNew;
end;
end;
Writeln(threadClass.ClassName, ' quick switches: ', quickSwitch);
Writeln('Duration: ', sw.ElapsedMilliseconds, ' ms');
Writeln('Call count: ', t.Count);
Writeln;
finally
t.Free;
end;
end;
procedure Main;
begin
TestPingPongThread(TMonitorThread, False);
TestPingPongThread(TYieldThread, False);
TestPingPongThread(TSpinWaitThread, False);
TestPingPongThread(TMonitorThread, True);
TestPingPongThread(TYieldThread, True);
TestPingPongThread(TSpinWaitThread, True);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Writeln('Press <Enter> to exit');
Readln;
end.
업데이트 : 이것은 섬유를 기반으로 구현했을 때보에만 약 5-6 배 느리게 수행
constructor TSpinEvent.Create;
begin
inherited Create(nil, False, False, '');
end;
procedure TSpinEvent.SetEvent;
begin
fState := 1;
inherited;
end;
procedure TSpinEvent.WaitFor;
var
startCount: Cardinal;
begin
startCount := TThread.GetTickCount;
while TInterlocked.CompareExchange(fState, 0, 1) <> 1 do
begin
if (TThread.GetTickCount - startCount) >= YieldTimeout then // YieldTimeout = 10
inherited WaitFor(INFINITE)
else
TThread.Yield;
end;
end;
: 나는 이벤트의 조합과 spinwait 함께했다
Ping 호출간에 작업을 추가 할 때 빠른 전환 및 1 % 미만의 속도 저하가 발생합니다. 물론 광섬유를 사용할 때 단 하나가 아닌 2 개의 코어로 작동합니다.
TMonitor로 할 수있는 몇 가지 조정 방법이 있습니다. [모니터 모니터링] (http://blogs.embarcadero.com/abauer/2013/08/23/38952) –
섬유를 사용하는 방법을 살펴볼 수도 있습니다. –
그것은 당신이 기대하는 것에 많이 의존합니다. 대부분 즉시 성공할 것인가 등등. 이상적으로는 몇 사이클 동안 회전하고 TMonitor 스타일 대기 상태로 전환하지 않는 것이 좋습니다. – Graymatter