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
。您只需要让它有一个窗口,并使用阻塞消息处理