为什么线程只与大间隔同步?

时间:2016-05-02 09:29:46

标签: linux multithreading zeromq freepascal

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版本

# 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)测试了代码,并减少了数据丢失的可能性。但你应该注意到这是一个幸运的黑客。

2 个答案:

答案 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