我有几个线程(Providers),由其他线程(Workers)并发使用。
根据线程,几个线程意味着severan关键部分。
将一个临界区变量置于一个线程类中是不可能的,还是更好地为每个需要同时访问的线程的关键部分保存单独的数组?
还是有更好的做法?
UPD:有点解释。
流程是:
这实际上在真实情况下效果很好,结果非常好。 但问题是当我需要杀死其中一个提供商时。
如果Provider类中有一个关键部分,那么一旦Worker已经进入CS并等待解锁并且提供者尚未真正终止,但终止下一步就可能获得访问冲突。
if not Provider.Terminated then
Provider.CS.Enter; //This is a place where AV occurs.
Provider.PostData(Worker.Data);
Provider.StartJob;
WaitForSingleObject(Provider.EventJobIsDone, 30000);
Provider.CS.Leave; //or here
如果我必须将CS放在外面 - 如何更好地执行此操作并测试提供程序是否已终止?
多个提供商和许多工人,随时创建/终止并应该一起工作。没有选项,一个Worker处理其所有数据池而其他人正在等待。每个工作人员进行一次迭代然后等待其他工作人员执行相同操作,然后再次执行下一次迭代然后再次等待,而其他工作人员执行相同操作,依此类推,直到所有池都被处理并且工作人员终止(每个人都在其自己的时间,当池时完了)。
每次迭代的工作者和结果都会影响下次为该工作者选择的提供者。
再次 - 这在真实条件下工作正常,不会发生卡住。非常快,并且预期会有效。
示例
一个粗略的例子。
你有工作线程,它从数据库加载数据池。有不同的DB,不仅仅是一个。每个项目 - 重磅图片0.1Mb-10Mb - 你以前从来不知道。想象一下,每个工人有100-5000件物品需要处理 - 你需要将它转换为较小的图片或做一些其他过程。你永远不会知道任何下一个工作线程中会有多少项。它可能是10,1000,5000,或多或少。每当一个Worker线程启动时 - 新的Worker线程都不应该超时开始处理数据池中的第一个项目,因为这样会浪费时间。
您有10个提供商,它们连接到本地网络上的10台计算机。这些机器可以远程完成您需要的所有工作。 (一个粗略的例子,请)
你应该平衡(真正的策略更复杂,取决于实际数据 - 它类似于:它是JPG,TIFF,PNG,BMP或任何格式,但只是请考虑平衡)所有提供商之间的流量正确。要做到这一点,你应该计算处理数据的数量,并决定每次迭代在哪里放置下一个工人调用 - 到Provider1,2,3 ...或10.此外你需要立即将结果返回给调用Worker,因为一个Worker应该立即报告,给DB写一个标志(只有Worker知道哪个DB)处理数据(!important)等等。
这就是条件。 简而言之:
如何告诉工作人员停止等待以及何时解冻 - 如何避免在工作流程的下一步(在上面的代码中显示)中非常确定的访问冲突。
答案 0 :(得分:0)
您的问题归结为以下情况:
这是一个设计问题,从不访问可以在不事先通知的情况下终止的线程。你可以通过一些沟通解决它。 也许让提供者活得足够长,告诉所有其他工作者线程,是时候说再见了。设置信号量,使用生命结束消息回答所有待处理的请求。在发送请求之前,请确保工作线程查看信号量。
答案 1 :(得分:0)
我对你所写的内容印象深刻,很多系统都不在你的掌控之中。我仍然有点不确定你可以改变多少。
我已经实现了一个全局Controller : TSystemController
类,用于控制所有部分。
我理解,终止提供商时,由您决定。 因此,我认为只有通过添加全局ProviderJob List / Queue才能避免AV。
工作人员将推送工作,提供者将弹出工作。 关键部分用于在推/弹期间由许多涉及的线程进行一致性。 我只使用了一个关键部分,因为此队列负责保存整个系统的所有作业。
A ProviderJob : TProviderJob
,将ThreadID保存到提供程序线程,以避免引用已终止/已释放的提供程序。除了WorkItem之外,还需要一个由worker提供的DoneEvent,因此Provider可以向等待的worker发出信号。
如果提供者突然被杀,那么工人将等待30秒并超时。
TProviderJob = class
FProviderThreadID : Cardinal;
FWorkItem : TWorkItem;
FWorkDoneEvent : TEvent;
public
property ProviderThreadID : Cardinal read FProviderThreadID;
property WorkItem : TWorkItem read FWorkItem;
property WorkDoneEvent : TEvent read FWorkDoneEvent;
end;
ProviderJob被Worker线程推送如下(没有直接引用Provider对象):
ProviderID := Controller.GetProviderThreadID;
Controller.PushWorkItemToProviderJobList( ProviderID, CurrentWorkItem, WorkDoneEvent );
工作人员将等到提供者完成该工作:
if WaitForSingleObjehect( WorkDoneEvent.Handle, 30000 ) = WAIT_OBJECT_0 then
begin
if CurrentWorkItem.State = wisProcessedOK then
begin
Inc(Controller.FWorkItemsDone);
Inc(NextWorkItemIdx);
if NextWorkItemIdx >= WorkItems.Count then
Terminate;
end;
end;
各个提供商正在按如下方式处理工作:
procedure TProviderThread.Execute;
var
WorkItem: TWorkItem;
WorkDoneEvent: TEvent;
begin
while not Terminated do
begin
Controller.PopNextWorkItemFromProviderJobList( self.ThreadID, WorkItem, WorkDoneEvent );
if (WorkItem<>nil) and (WorkDoneEvent<>nil) then
begin
WorkItem.FState := wisStarted;
Sleep( Round( Random( 5000 )));
WorkItem.FState := wisProcessedOK;
WorkDoneEvent.SetEvent;
end
else
Sleep(500);
end;
end;
这是一个例子: Screenshot of the test app
以下是完整的解决方案:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 248
ClientWidth = 477
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Memo1: TMemo
Left = 8
Top = 8
Width = 369
Height = 209
Lines.Strings = (
'Memo1')
TabOrder = 0
end
object bStart: TButton
Left = 383
Top = 72
Width = 75
Height = 25
Caption = 'Start'
TabOrder = 1
OnClick = bStartClick
end
object bStop: TButton
Left = 383
Top = 103
Width = 75
Height = 25
Caption = 'Stop'
TabOrder = 2
OnClick = bStopClick
end
object bKillProvider: TButton
Left = 383
Top = 134
Width = 75
Height = 25
Caption = 'Kill Provider'
TabOrder = 3
OnClick = bKillProviderClick
end
object Timer1: TTimer
OnTimer = Timer1Timer
Left = 400
Top = 8
end
end
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Math;
type
TForm1 = class(TForm)
Timer1: TTimer;
Memo1: TMemo;
bStart: TButton;
bStop: TButton;
bKillProvider: TButton;
procedure Timer1Timer(Sender: TObject);
procedure bKillProviderClick(Sender: TObject);
procedure bStopClick(Sender: TObject);
procedure bStartClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
System.SyncObjs,
Generics.Collections;
type
TWorkItemStateE = ( wisNotStarted, wisStarted, wisProcessedOK );
TWorkItem = class
private
FState : TWorkItemStateE;
FID : Integer;
public
property ID : Integer read FID;
property State: TWorkItemStateE read FState;
end;
TWorkItemList = class(TObjectList<TWorkItem>);
TWorkerThread = class(TThread)
protected
procedure Execute; override;
end;
TWorkerThreadList = class(TObjectList<TWorkerThread>);
TProviderThread = class(TThread)
protected
procedure Execute; override;
end;
TProviderThreadList = TObjectList<TProviderThread>;
TProviderJob = class
FProviderThreadID : Cardinal;
FWorkItem : TWorkItem;
FWorkDoneEvent : TEvent;
public
property ProviderThreadID : Cardinal read FProviderThreadID;
property WorkItem : TWorkItem read FWorkItem;
property WorkDoneEvent : TEvent read FWorkDoneEvent;
end;
TProviderJobList = TObjectList<TProviderJob>;
TSystemContoller = class
private
FProviders: TProviderThreadList;
FWorkers : TWorkerThreadList;
FGlobalProviderJobList : TProviderJobList;
FCS_GlobalProviderJobList: TCriticalSection;
FStarted : Boolean;
FRemovedProviders: Integer;
FKilledProviders: Integer;
FRemovedWorkers: Integer;
FWorkItemsDone: Integer;
FStartedDateTime: TDateTime;
procedure ThreadTerminated(Sender: TObject);
procedure AddProvider;
procedure AddWorker;
public
constructor Create;
destructor Destroy; override;
function GetProvider : TProviderThread;
function GetProviderThreadID: Cardinal;
procedure PopNextWorkItemFromProviderJobList(
const AProviderThreadID: Cardinal;
out NextWorkItem: TWorkItem;
out NextWorkDoneEvent: TEvent);
procedure PushWorkItemToProviderJobList(
AProviderThreadID: Cardinal;
AWorkItem: TWorkItem;
AWorkDoneEvent: TEvent);
procedure Start;
procedure Stop;
procedure KillProvider;
function GetStatusReport : String;
end;
var
Controller : TSystemContoller;
{ TWorkThread }
procedure TWorkerThread.Execute;
procedure LoadWorkItems( AWorkItems : TWorkItemList );
var
WorkItemCount: Integer;
n : Integer;
WorkItem: TWorkItem;
begin
// Load work items:
WorkItemCount := 1+Round(Random(10));
for n := 1 to WorkItemCount do
begin
WorkItem := TWorkItem.Create;
WorkItem.FID := n;
AWorkItems.Add(WorkItem);
end;
end;
var
WorkItems : TWorkItemList;
ProviderID: Cardinal;
NextWorkItemIdx: Integer;
CurrentWorkItem: TWorkItem;
WorkDoneEvent : TEvent;
begin
WorkItems := TWorkItemList.Create;
WorkDoneEvent := TEvent.Create(nil, False, False, '' );
try
// load:
LoadWorkItems( WorkItems );
// process work items:
NextWorkItemIdx := 0;
while not Terminated do
begin
CurrentWorkItem := WorkItems[ NextWorkItemIdx ];
ProviderID := Controller.GetProviderThreadID;
Controller.PushWorkItemToProviderJobList( ProviderID, CurrentWorkItem, WorkDoneEvent );
if WaitForSingleObject( WorkDoneEvent.Handle, 30000 ) = WAIT_OBJECT_0 then
begin
if CurrentWorkItem.State = wisProcessedOK then
begin
Inc(Controller.FWorkItemsDone);
Inc(NextWorkItemIdx);
if NextWorkItemIdx >= WorkItems.Count then
Terminate;
end;
end;
Sleep(1000);
end;
finally
WorkDoneEvent.Free;
WorkItems.Free;
end;
end;
{ TProviderThread }
procedure TProviderThread.Execute;
var
WorkItem: TWorkItem;
WorkDoneEvent: TEvent;
begin
while not Terminated do
begin
Controller.PopNextWorkItemFromProviderJobList( self.ThreadID, WorkItem, WorkDoneEvent );
if (WorkItem<>nil) and (WorkDoneEvent<>nil) then
begin
WorkItem.FState := wisStarted;
Sleep( Round( Random( 5000 )));
WorkItem.FState := wisProcessedOK;
WorkDoneEvent.SetEvent;
end
else
Sleep(500);
end;
end;
{ TSystemContoller }
constructor TSystemContoller.Create;
begin
inherited;
FStartedDateTime:= now;
FCS_GlobalProviderJobList := TCriticalSection.Create;
FGlobalProviderJobList := TProviderJobList.Create;
FProviders:= TProviderThreadList.Create;
FProviders.OwnsObjects := False;
FWorkers := TWorkerThreadList.Create;
FWorkers.OwnsObjects := False;
end;
destructor TSystemContoller.Destroy;
begin
FCS_GlobalProviderJobList.Free;
FGlobalProviderJobList.Free;
FWorkers.Free;
FProviders.Free;
inherited;
end;
procedure TSystemContoller.Start;
var
n: Integer;
begin
if not FStarted then
begin
FStarted := True;
for n := 1 to 5 do
AddProvider;
for n := 1 to 10 do
AddWorker;
end;
end;
procedure TSystemContoller.Stop;
var
n: Integer;
begin
for n := FProviders.Count-1 to 0 do
FProviders[n].Terminate;
for n := FWorkers.Count-1 to 0 do
FWorkers[n].Terminate;
FStarted := False;
end;
procedure TSystemContoller.KillProvider;
var
Provider: TProviderThread;
begin
Provider := GetProvider;
if Provider<>nil then
begin
if not Provider.Terminated then
begin
GetProvider.Terminate;
Inc( FKilledProviders );
end;
end;
end;
procedure TSystemContoller.AddProvider;
var
Provider: TProviderThread;
begin
Provider := TProviderThread.Create(True);
Provider.OnTerminate := ThreadTerminated;
Provider.FreeOnTerminate := True;
FProviders.Add( Provider );
Provider.Start;
end;
procedure TSystemContoller.AddWorker;
var
Worker: TWorkerThread;
begin
Worker := TWorkerThread.Create(True);
Worker.OnTerminate := ThreadTerminated;
Worker.FreeOnTerminate := True;
FWorkers.Add( Worker );
Worker.Start;
end;
procedure TSystemContoller.ThreadTerminated(Sender : TObject );
begin
if Sender is TProviderThread then
begin
FProviders.Remove(TProviderThread(Sender));
Inc(FRemovedProviders);
if FStarted then
AddProvider;
end
else
if Sender is TWorkerThread then
begin
FWorkers.Remove(TWorkerThread(Sender));
Inc(FRemovedWorkers);
if FStarted then
AddWorker;
end;
end;
procedure TSystemContoller.PushWorkItemToProviderJobList(
AProviderThreadID: Cardinal;
AWorkItem: TWorkItem;
AWorkDoneEvent: TEvent);
var
ProviderJob: TProviderJob;
begin
FCS_GlobalProviderJobList.Enter;
try
ProviderJob := TProviderJob.Create;
ProviderJob.FProviderThreadID := AProviderThreadID;
ProviderJob.FWorkItem := AWorkItem;
ProviderJob.FWorkDoneEvent := AWorkDoneEvent;
FGlobalProviderJobList.Add( ProviderJob );
finally
FCS_GlobalProviderJobList.Leave;
end;
end;
procedure TSystemContoller.PopNextWorkItemFromProviderJobList(
const AProviderThreadID: Cardinal;
out NextWorkItem: TWorkItem;
out NextWorkDoneEvent: TEvent);
var
n : Integer;
begin
FCS_GlobalProviderJobList.Enter;
try
NextWorkItem := nil;
NextWorkDoneEvent := nil;
for n := 0 to FGlobalProviderJobList.Count-1 do
begin
if FGlobalProviderJobList[n].ProviderThreadID = AProviderThreadID then
begin
NextWorkItem := FGlobalProviderJobList[n].WorkItem;
NextWorkDoneEvent := FGlobalProviderJobList[n].WorkDoneEvent;
FGlobalProviderJobList.Delete(n);
Exit;
end;
end;
finally
FCS_GlobalProviderJobList.Leave;
end;
end;
function TSystemContoller.GetProvider: TProviderThread;
var
ProviderIdx: Integer;
begin
ProviderIdx := Trunc(Random( FProviders.Count ));
if InRange(ProviderIdx, 0, FProviders.Count-1 ) then
Result := FProviders[ ProviderIdx ]
else
Result := nil;
end;
function TSystemContoller.GetProviderThreadID: Cardinal;
var
Provider: TProviderThread;
begin
Provider := GetProvider;
if Provider<>nil then
Result := Provider.ThreadID;
end;
function TSystemContoller.GetStatusReport: String;
const
cState : array[Boolean] of string = ( 'Stopped', 'Started' );
begin
Result := 'Start Date and Time: ' + DateTimeToStr(FStartedDateTime) + #13#10+
'Date and Time: ' + DateTimeToStr(now) + #13#10+
'System State: ' + cState[FStarted] + #13#10+ #13#10 +
'Queued Work Items: ' + IntToStr( self.FGlobalProviderJobList.Count )+ #13#10 +
'Work Items Done: ' + IntToStr(FWorkItemsDone)+ #13#10 + #13#10 +
'Current Providers: ' + IntToStr( self.FProviders.Count ) + #13#10+
'Removed Providers: ' + IntToStr( FRemovedProviders ) + #13#10 +
'Random Provider Kills: ' + IntToStr(FKilledProviders)+ #13#10 + #13#10 +
'Current Workers: ' + IntToStr( self.FWorkers.Count ) + #13#10 +
'Removed Workers: ' + IntToStr( FRemovedWorkers );
end;
procedure TForm1.bKillProviderClick(Sender: TObject);
begin
Controller.KillProvider;
end;
procedure TForm1.bStartClick(Sender: TObject);
begin
Controller.Start;
end;
procedure TForm1.bStopClick(Sender: TObject);
begin
Controller.Stop;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Memo1.Text := Controller.GetStatusReport;
if Random(100) < 30 then
Controller.KillProvider;
end;
initialization
Controller := TSystemContoller.Create;
Controller.Start;
finalization
Controller.Stop;
Controller.Free;
end.