Multithreading 线程同步队列的最佳方法

Multithreading 线程同步队列的最佳方法,multithreading,delphi,queue,synchronous,Multithreading,Delphi,Queue,Synchronous,我有一个队列,可以让不同的线程排队,因此我可以保证两件事: 请求被逐一处理 在到达订单中处理请求 第二点很重要。否则,一个简单的临界截面就足够了。 我有不同的请求组,只有在单个组中才能满足这些要求。来自不同组的请求可以并发运行 看起来是这样的: FTaskQueue.Enqueu('MyGroup'); try Do Something (running in context of some thread) finally FTaskQueue.Dequeu('MyGroup'); en

我有一个队列,可以让不同的线程排队,因此我可以保证两件事:

  • 请求被逐一处理
  • 在到达订单中处理请求
  • 第二点很重要。否则,一个简单的临界截面就足够了。 我有不同的请求组,只有在单个组中才能满足这些要求。来自不同组的请求可以并发运行

    看起来是这样的:

    FTaskQueue.Enqueu('MyGroup');
    try
      Do Something (running in context of some thread)
    finally
      FTaskQueue.Dequeu('MyGroup');
    end;
    
    procedure Enqueue;
    begin
      EnterCriticalSection(FCritSec);
      try
        DoEnqueue;
      finally 
        LeaveCriticalSection(FCritSec);
      end;
    
      BlockTheCurrentThread; // here the thread blocks itself
    end;
    
    procedure Dequeue;
    begin
      EnterCriticalSection(FCritSec);
      try
        DoDequeue;
        UnblockTheNextThread; // here the thread unblocks another thread
      finally 
        LeaveCriticalSection(FCritSec);
      end;
    end;
    
    编辑:我删除了实际实现,因为它隐藏了我想要解决的问题

    我需要这个,因为我有一个基于Indy的web服务器,它接受http请求。首先,我为请求找到一个共同响应会话。然后为该会话执行请求(代码)。我可以为同一个会话获取多个请求(读取,当第一个请求仍在处理时,我可以获取新请求),并且它们必须按照正确的到达顺序逐个执行。因此,我寻找一个通用的同步队列,可以在这种情况下使用,这样请求就可以排队了。我无法控制线程,每个请求可能在不同的线程中执行

    解决这类问题的最佳(通常)方法是什么?问题在于排队和出列必须是原子操作,这样才能保持正确的顺序。我当前的实现有一个很大的瓶颈,但它可以工作

    编辑:下面是原子入/出队列操作的问题

    你通常会这样做:

    FTaskQueue.Enqueu('MyGroup');
    try
      Do Something (running in context of some thread)
    finally
      FTaskQueue.Dequeu('MyGroup');
    end;
    
    procedure Enqueue;
    begin
      EnterCriticalSection(FCritSec);
      try
        DoEnqueue;
      finally 
        LeaveCriticalSection(FCritSec);
      end;
    
      BlockTheCurrentThread; // here the thread blocks itself
    end;
    
    procedure Dequeue;
    begin
      EnterCriticalSection(FCritSec);
      try
        DoDequeue;
        UnblockTheNextThread; // here the thread unblocks another thread
      finally 
        LeaveCriticalSection(FCritSec);
      end;
    end;
    

    现在的问题是,这不是原子的。如果队列中已经有一个线程,而另一个线程来调用Enqueue,那么第二个线程可能会离开关键部分,并尝试阻塞自身。现在,线程调度程序将恢复第一个线程,该线程将尝试取消阻止下一个(第二个)线程。但第二个线程还没有被阻塞,所以什么也没发生。现在第二个线程继续并阻塞自身,但这是不正确的,因为它不会被解除阻塞。如果阻塞在关键部分内,那么关键部分将永远不会离开,我们将处于死锁状态。

    我将根据您的评论中的附加信息进行回答

    如果有许多线程需要序列化,那么可以免费使用Windows提供的序列化机制。让每个队列都是一个线程,具有自己的窗口和标准消息循环。使用
    SendMessage()
    而不是
    PostThreadMessage()
    ,Windows将负责阻止发送线程,直到消息被处理完毕,并确保保持正确的执行顺序。通过为每个请求组使用具有自己窗口的线程,您可以确保多个组仍然被并发处理

    这是一个简单的解决方案,只有当请求本身可以在不同的线程上下文中处理时才有效,在许多情况下,这应该不是问题。

    另一种方法:

    让每个请求线程都有一个最初未设置的手动重置事件。队列管理器是一个简单的对象,它维护此类事件的线程安全列表。
    Enqueue()
    Dequeue()
    方法都将请求线程的事件作为参数

    type
      TRequestManager = class(TObject)
      strict private
        fCritSect: TCriticalSection;
        fEvents: TList<TEvent>;
      public
        constructor Create;
        destructor Destroy; override;
    
        procedure Enqueue(ARequestEvent: TEvent);
        procedure Dequeue(ARequestEvent: TEvent);
      end;
    
    { TRequestManager }
    
    constructor TRequestManager.Create;
    begin
      inherited Create;
      fCritSect := TCriticalSection.Create;
      fEvents := TList<TEvent>.Create;
    end;
    
    destructor TRequestManager.Destroy;
    begin
      Assert((fEvents = nil) or (fEvents.Count = 0));
      FreeAndNil(fEvents);
      FreeAndNil(fCritSect);
      inherited;
    end;
    
    procedure TRequestManager.Dequeue(ARequestEvent: TEvent);
    begin
      fCritSect.Enter;
      try
        Assert(fEvents.Count > 0);
        Assert(fEvents[0] = ARequestEvent);
        fEvents.Delete(0);
        if fEvents.Count > 0 then
          fEvents[0].SetEvent;
      finally
        fCritSect.Release;
      end;
    end;
    
    procedure TRequestManager.Enqueue(ARequestEvent: TEvent);
    begin
      fCritSect.Enter;
      try
        Assert(ARequestEvent <> nil);
        if fEvents.Count = 0 then
          ARequestEvent.SetEvent
        else
          ARequestEvent.ResetEvent;
        fEvents.Add(ARequestEvent);
      finally
        fCritSect.Release;
      end;
    end;
    
    队列管理器在
    Enqueue()
    Dequeue()中锁定事件列表。如果
    Enqueue()
    中的列表为空,则会在参数中设置事件,否则会重置事件。然后将事件附加到列表中。因此,第一个线程可以继续请求,所有其他线程都将阻塞。在
    Dequeue()


    这样,最后一个请求线程将导致下一个请求线程解除阻塞,完全不挂起或恢复线程。此解决方案也不需要任何额外的线程或窗口,每个请求线程只需要一个事件对象。

    您是否尝试了Delphi提供的TThreadList对象

    它是线程安全的,可以为您管理锁。您可以在主线程内的线程外管理列表


    当请求要求执行新任务时,您可以将其添加到列表中。当一个线程完成时,通过OnTerminate事件,您可以调用列表中的下一个线程

    如果您有多个线程彼此挂起并恢复,以确保在任何给定时间只有一个线程执行,那么您应该意识到您的整个设计是错误的。在这种情况下,一个请求不应该等于一个线程。我怀疑这种方法是不正确的。但假设我有一台印地服务器。我在web服务器事件处理程序中获取http请求。我通过哈希表找到一个会话,然后执行请求=为该会话执行一些代码。现在,如果对于同一个会话,我收到多个请求,它们必须一个接一个地执行。每个线程都将处于不同的线程上下文中。我无法控制线程。如果你知道一个更好的方法,那么请把它写下来作为一个答案。我明白了,但是从你的问题中根本看不到这些限制。我认为,如果您删除自己解决方案的详细信息,只说明问题并询问解决方法,您会得到更好的答案。请注意,这与序列化对STA COM对象的方法调用的原理相同。没错,这可能是一个很好的解决方案,是的,引入新线程不是问题,那就是监听消息。唯一的缺点(除了虚拟窗口)是,这样,我需要另一个线程池。我已经有非常好的线程从印地服务器。但这可能是唯一可行的解决办法。如果有人有另一种方法,请稍候。注意,没有新线程,因为您已经有了
    TTaskQueueThread
    。您只需要让它有一个窗口,并使用阻塞消息处理