FPC 3.0.0,Lazarus 1.6.0
我在Linux机器上有两个线程(稍后我会检查Windows,这个代码应该是跨平台的),主线程和客户端线程。客户端线程的目的是提供非阻塞请求 - 回复网络功能。
pascal
客户端(请求者)与服务器通信,用python
(replier)编写。
我正在使用ZeroMQ
REQ/REP
来处理最低级别的版本。
在高级别中,创建和启动客户端线程的方式如下:
//from the main thread
FClientThread.Create('tcp://localhost:5020');
FClientThread.Start;
发送请求:
//from the main thread
FClientThread.SendRequest('im_requesting_stuff',-1,'give_it_to_me');
Sleep(1000)
FClientThread.SendRequest('requesting',-1,'gimme_more');
//the TClientThread descends from TThread.
procedure TClientThread.SendRequest(ACode: string; ATrialIndex: integer;
ARequest: string);
begin
FRequest := ARequest;
FCode := ACode;
FTrialIndex := IntToStr(ATrialIndex);
RTLeventSetEvent(FRTLEvent);
end;
procedure TClientThread.Execute;
var
AMessage : UTF8String;
begin
while not Terminated do
begin
AMessage := '';
RTLeventWaitFor(FRTLEvent);
FRequester.send( FRequest );
FRequester.recv( AMessage ); // blocking code,
// that is why it is inside the thread
// *****************************************************************************
// BUG: These vars are being filled with the last values of
// ( 'FTrialIndex', 'AMessage', 'FCode' )
FMsg := #40#39 + FTrialIndex + #39#44#32#39 + AMessage + #39#44#32#39 + FCode + #39#41;
//
// *****************************************************************************
Synchronize( @Showstatus );
end;
end;
服务器端以非阻塞方式工作,单线程:
while True:
msg = self.socket.recv(flags=zmq.NOBLOCK)
# do stuff
self.socket.send(response)
只有在非常短的时间间隔内多次调用.SendRequest()
(例如,没有sleep()
)时,才会发出评论错误(重复上次调用)。
我原本期待一些损失,但我并没有期待重复,因为我明确地等待主线程返回。
有什么建议吗?
最好的问候
编辑1:
# zmq
print zmq.zmq_version()
# 4.1.2
# pyzmq
print zmq.__version__
# 15.1.0
对于delphizmq版本,请参阅commit(50a28b4b72c531536452ee5d79e19f5960c9f7c7)。
// zmq
ZMQVersion(minor,major,patch)
WriteLn(minor,major,patch)
// 3.2.5
编辑2:
图例:
LogTickCount:('REQTickCount','TestCode:DeltaTime')
在将Fvariables复制到局部变量之前:
4476.028775652 : [debug] TClockThread.Execute:Start 140736007759616
4476.087363892 : ('4476.08701411', 'S:7.316086894')
4478.567764596 : ('4478.56744415', '*R:9.828251263')
4478.923743380 : ('4478.92343718', 'R:10.192552661')
4479.134910639 : [debug] C: expected next
4479.166311115 : ('4479.16600296', 'R:10.424417498') // BUG
4479.201287606 : ('4479.20099538', 'R:10.424417498')
4483.037278107 : ('4483.03664644', '2b:14.322538449')
4483.868644929 : ('4483.86663063', '*R:15.153748820')
4484.278650579 : ('4484.27829493', 'R:15.562894551')
4484.552199446 : [debug] C: expected next
// data lost
4484.594517381 : ('4484.59364657', 'R:15.841710775')
4490.088956444 : ('4490.08862455', '1b:21.318677098')
4490.465529156 : ('4490.46522659', '*R:21.738692793')
4490.744151772 : ('4490.74376093', 'R:22.027325175')
4491.047253601 : [debug] C: expected next
4491.064188821 : ('4491.0638315', 'R:22.336764091') // BUG
4491.100142009 : ('4491.09978804', 'R:22.336764091')
4496.084726117 : ('4496.08441014', '2a:27.320218402')
4496.494107080 : ('4496.49378719', '*R:27.762184396')
4496.816623032 : ('4496.81630259', 'R:28.100685410')
4497.088603481 : [debug] C: expected next
// data lost
4497.132445971 : ('4497.13214788', 'R:28.378107825')
将Fvariables复制到局部变量后(注意:纯幸运的数据没有丢失):
8183.233467050 : [debug] TClockThread.Execute:Start 140736477513472
8183.299773049 : ('8183.29946881', 'S:10.678368191')
8183.751976738 : ('8183.7516616', '*R:11.182574383')
8184.262027198 : ('8184.26170452', 'R:11.690292332')
8184.501553063 : [debug] C: is expected next
8184.517362396 : ('8184.51705341', 'C:11.952652441')
8184.552388891 : ('8184.55206258', 'R:11.950063743')
8189.291033037 : ('8189.29070225', '2b:16.683417064')
8189.880999859 : ('8189.88067746', '*R:17.299351333')
8190.257476710 : ('8190.25714788', 'R:17.699402216')
8190.515557895 : [debug] C: is expected next
8190.553234830 : ('8190.55290992', 'C:17.966527084')
8190.588187291 : ('8190.5878607', 'R:17.964049297')
8196.243242766 : ('8196.24264239', '1b:23.683231358')
8196.758162472 : ('8196.75784342', '*R:24.200630247')
8196.964202600 : ('8196.96383131', 'R:24.397590095')
8197.119091206 : [debug] C: is expected next
8197.142731450 : ('8197.14240346', 'C:24.570427216')
8197.177078782 : ('8197.17673878', 'R:24.567590990')
8208.243614030 : ('8208.24296169', '2a:35.685189479')
8208.659747197 : ('8208.65940133', '*R:36.092803071')
8208.794614317 : ('8208.79431818', 'R:36.230488258')
8208.909275842 : [debug] C: is expected next
8208.935924407 : ('8208.93559782', 'C:36.360198934')
8208.970681025 : ('8208.9703712', 'R:36.357781493')
事实证明,对于我的具体实施,不需要使用关键部分。
编辑3。:
请注意,目前的问题不有关数据丢失(排队请求)。它是关于线程同步(变量共享)。关于数据丢失,使用Sleep()
只是一个黑客攻击(你应该使用队列)。
我已使用Sleep(10)
测试了代码,并减少了数据丢失的可能性。但你应该注意到这是一个幸运的黑客。
答案 0 :(得分:2)
您需要一些锁定机制来读取和写入来自不同线程的值。
只有很小的锁定时间才能复制数据。
TClientThread = class( TThread )
private
FCriticalSection: TCriticalSection;
...
end;
//the TClientThread descends from TThread.
procedure TClientThread.SendRequest(ACode: string; ATrialIndex: integer;
ARequest: string);
begin
FCriticalSection.Enter;
try
FRequest := ARequest;
FCode := ACode;
FTrialIndex := IntToStr(ATrialIndex);
finally
FCriticalSection.Leave;
end;
RTLeventSetEvent(FRTLEvent);
end;
procedure TClientThread.Execute;
var
lRequest: string;
AMessage : UTF8String;
begin
while not Terminated do
begin
AMessage := '';
RTLeventWaitFor(FRTLEvent);
FCriticalSection.Enter;
try
// copy data to local vars
lRequest := FRequest;
finally
FCriticalSection.Leave;
end;
FRequester.send( lRequest );
...
答案 1 :(得分:1)
可能是因为调度。您使用FClientThread.Create('tcp://localhost:5020');
创建了一个可执行代码(该线程)并告诉O.S. "线程准备就绪,您可以随时随地安排线程"与FClientThread.Start;
使用FClientThread.SendRequest('im_requesting_stuff',-1,'give_it_to_me');
发送参数并不能保证您的线程会立即运行。当前上下文将与主线程和调度程序无法启动您新创建的线程。并且Sleep(1000)
在调度视角内没有太大变化。如果您的代码与Sleep(1000)
一起使用,请相信我的纯粹运气;为什么?您无法确定调度程序何时调度您的线程,这就是原因..因此,当您调用FClientThread.SendRequest('requesting',-1,'gimme_more');
时,您仍然在主线程中并设置客户端线程的变量。每当客户端线程被调度时,它就会使用当前变量,在本例中设置了两次。
我认为应该在SendRequest
方法中实现阻塞参数传递机制。并且确保有很多方法可以实现。
简而言之,它主要是关于线程同步和调度。
PS:Sleep(0)
可以解决问题,但不仅仅是彻底解决问题。同步问题仍然存在,但第二个线程只能获得调度并在给定的量子时间内完成I / O的机会。
PS2:Sleep(0)
:"嘿,O.S。我完成了,你不需要等到你指定给我的时间,打断我。在这里你可以随时使用,我已经完成了它。如果你愿意的话,你可以安排其他任何人(如果没有其他人,你可以再安排我)。 Sleep Function MSDN
和引文:
休眠间隔过后,线程就可以运行了。如果指定0毫秒,则线程将放弃其时间片的剩余部分但仍保持准备状态。请注意,不保证立即运行就绪线程。因此,线程可能在睡眠间隔过去一段时间后才会运行。有关详细信息,请参阅Scheduling Priorities。