TThreadedQueue not capable of multiple consumers?

前端 未结 5 530
灰色年华
灰色年华 2020-11-29 02:11

Trying to use the TThreadedQueue (Generics.Collections) in a single producer multiple consumer scheme. (Delphi-XE). The idea is to push objects into a queue and let several

相关标签:
5条回答
  • 2020-11-29 02:41

    I recommend you to use OmniThreadLibrary http://www.thedelphigeek.com/search/label/OmniThreadLibrary when working with threads, parallelism, etc. Primoz made a very good job, and on the site you'll find a lot of useful documentation.

    0 讨论(0)
  • 2020-11-29 02:42

    Your example seems to work fine under XE2, but if we fill your queue it fails with AV on a PushItem. (Tested under XE2 Update1)

    To reproduce, just increase your task creation from 100 to 1100 (your queue depth was set at 1024)

    for i:= 1 to 1100 do fThreadQueue.PushItem( TThreadTaskMsg.Create( i,''));
    

    This dies for me every time on Windows 7. I initially tried a continual push to stress test it, and it failed at loop 30...then at loop 16...then at 65 so at different intervals but it consistently failed at some point.

      iLoop := 0;
      while iLoop < 1000 do
      begin
        Inc(iLoop);
        WriteLn('Loop: ' + IntToStr(iLoop));  
        for i:= 1 to 100 do fThreadQueue.PushItem( TThreadTaskMsg.Create( i,''));
      end;
    
    0 讨论(0)
  • 2020-11-29 02:54

    Well, it's hard to be sure without a lot of testing, but it certainly looks like this is a bug, either in TThreadedQueue or in TMonitor. Either way it's in the RTL and not your code. You ought to file this as a QC report and use your example above as the "how to reproduce" code.

    0 讨论(0)
  • 2020-11-29 02:54

    I looked for the TThreadedQueue class but don't seem to have it in my D2009. I'm not exactly going to kill myself over this - Delphi thread support has always been err.. errm... 'non-optimal' and I suspect that TThreadedQueue is no different :)

    Why use generics for P-C (Producer / Consumer) objects? A simple TObjectQueue descendant will do fine - been using this for decades - works fine with multiple producers/consumers:

    unit MinimalSemaphorePCqueue;
    
    { Absolutely minimal P-C queue based on TobjectQueue and a semaphore.
    
    The semaphore count reflects the queue count
    'push' will always succeed unless memory runs out, then you're stuft anyway.
    'pop' has a timeout parameter as well as the address of where any received
    object is to be put.
    'pop' returns immediately with 'true' if there is an object on the queue
    available for it.
    'pop' blocks the caller if the queue is empty and the timeout is not 0.
    'pop' returns false if the timeout is exceeded before an object is available
    from the queue.
    'pop' returns true if an object is available from the queue before the timeout
    is exceeded.
    If multiple threads have called 'pop' and are blocked because the queue is
    empty, a single 'push' will make only one of the waiting threads ready.
    
    
    Methods to push/pop from the queue
    A 'semaHandle' property that can be used in a 'waitForMultipleObjects' call.
    When the handle is signaled, the 'peek' method will retrieve the queued object.
    }
    interface
    
    uses
      Windows, Messages, SysUtils, Classes,syncObjs,contnrs;
    
    
    type
    
    pObject=^Tobject;
    
    
    TsemaphoreMailbox=class(TobjectQueue)
    private
      countSema:Thandle;
    protected
      access:TcriticalSection;
    public
      property semaHandle:Thandle read countSema;
      constructor create; virtual;
      procedure push(aObject:Tobject); virtual;
      function pop(pResObject:pObject;timeout:DWORD):boolean;  virtual;
      function peek(pResObject:pObject):boolean;  virtual;
      destructor destroy; override;
    end;
    
    
    implementation
    
    { TsemaphoreMailbox }
    
    constructor TsemaphoreMailbox.create;
    begin
    {$IFDEF D2009}
       inherited Create;
    {$ELSE}
      inherited create;
    {$ENDIF}
      access:=TcriticalSection.create;
      countSema:=createSemaphore(nil,0,maxInt,nil);
    end;
    
    destructor TsemaphoreMailbox.destroy;
    begin
      access.free;
      closeHandle(countSema);
      inherited;
    end;
    
    function TsemaphoreMailbox.pop(pResObject: pObject;
      timeout: DWORD): boolean;
    // dequeues an object, if one is available on the queue.  If the queue is empty,
    // the caller is blocked until either an object is pushed on or the timeout
    // period expires
    begin // wait for a unit from the semaphore
      result:=(WAIT_OBJECT_0=waitForSingleObject(countSema,timeout));
      if result then // if a unit was supplied before the timeout,
      begin
        access.acquire;
        try
          pResObject^:=inherited pop; // get an object from the queue
        finally
          access.release;
        end;
      end;
    end;
    
    procedure TsemaphoreMailbox.push(aObject: Tobject);
    // pushes an object onto the queue.  If threads are waiting in a 'pop' call,
    // one of them is made ready.
    begin
      access.acquire;
      try
        inherited push(aObject); // shove the object onto the queue
      finally
        access.release;
      end;
      releaseSemaphore(countSema,1,nil); // release one unit to semaphore
    end;
    
    function TsemaphoreMailbox.peek(pResObject: pObject): boolean;
    begin
      access.acquire;
      try
        result:=(count>0);
        if result then pResObject^:=inherited pop; // get an object from the queue
      finally
        access.release;
      end;
    end;
    end.
    
    0 讨论(0)
  • 2020-11-29 02:56

    I don't think TThreadedQueue is supposed to support multiple consumers. It's a FIFO, as per the help file. I am under the impression that there's one thread pushing and another one (just one!) popping.

    0 讨论(0)
提交回复
热议问题