【问题标题】:Selective critical section - conditional选择性临界区 - 有条件的
【发布时间】:2011-04-14 09:07:56
【问题描述】:

我有一个以数据库表作为参数的线程,我遇到了一个问题,我无法同时写入该数据库表。

TMyThread 的 1 个实例可以有一个 'Member' 的 db 表,而另一个可能有 'Staff' 但是可能存在两个线程打开同一个表的情况。

因此,我需要将代码包装在关键部分(或类似部分)中,但我不想要一些像 (fMemberTable, fStaffTable) 这样的关键部分之类的脏东西...

begin
    if fDBTable = 'Member' then
        fMemberTable.Enter
    else if fDbTable = 'Staff' then
    ....

我们有 8 张桌子,所以会很乱 有什么办法吗

TCricalSection(fMemberTable).Enter; 还是某种易于“扩展”和使用的方法?

围绕函数的一个关键部分没有意义,因为我不想阻止其他表......

【问题讨论】:

  • 通常数据库非常擅长管理并发性。你用的是什么数据库?有办法在数据库层而不是在应用程序层进行管理。
  • 使用来自 Aidaim 的 Accuracer - 支持这一点,但我们发现了问题,已经发送了一封包含示例代码的电子邮件以支持。我想要一个备用计划,以防他们需要一周的时间来修复它。 (他们的问题不是每个表,而是每个数据库。但他们会支持它)

标签: multithreading delphi thread-safety critical-section


【解决方案1】:

你可以这样做:

TMonitor.Enter(fMemberTable);
try
  // Do your stuff
finally TMonitor.Exit(fMemberTable);
end;

请注意,这是一个 SPIN LOCK,而不是真正的临界区。如果您不会发生很多冲突,这非常实用,但是如果线程定期相互阻塞,您可能希望退回到临界区。根据定义,自旋锁是忙等待锁。

但我不确定 Delphi 的哪个版本引入了此功能,并且您没有特定于版本的标签。

【讨论】:

  • TMonitor 是在 Delphi 2009 中引入的。
  • 如果不出意外,您可以拥有一个临界区“工厂”,它将表格作为输入参数,然后可以模拟类似于 TMonitor 的东西。
  • Windows 提供(自 Windows 2000 起)一个不错的 InitializeCriticalSectionAndSpinCount() 调用。它将进入指定计数的忙碌等待,如果无法及时进入该部分,它将使用更便宜的等待。不幸的是,Delphi 经常尝试支持 Win95、Win 3.0、DOS 1.0 和 CP/M...
  • TMonitor 似乎想要一个对象,这是否意味着它会锁定所有表?
  • 您关于 TMonitor 使用自旋锁的想法只是部分正确,因为 a) 它只会在多 cpu 系统中使用自旋锁 b) 自旋锁的使用是可选的 - 您必须设置 Monitor 的 SpinCount 否则调用线程阻塞等待获取 Monitor。
【解决方案2】:

您可以使用关键部分列表,例如,本单元中定义的 My class:

interface
uses Classes, SyncObjs;

type
  { TCriticalSectionList by jachguate }
  { http://jachguate.wordpress.com }
  TCriticalSectionList = class
  private
    FCSList: TThreadList;
    FNameList: TStringList;
    function GetByName(AName: string): TCriticalSection;
  public
    constructor Create();
    destructor Destroy(); override;
    property ByName[AName: string]: TCriticalSection read GetByName; default;
  end;

  function CSList: TCriticalSectionList;

implementation
uses SysUtils;

{ TCriticalSectionList }

constructor TCriticalSectionList.Create;
begin
  inherited;
  FCSList := TThreadList.Create;
  FNameList := TStringList.Create;
end;

destructor TCriticalSectionList.Destroy;
var
  I: Integer;
  AList: TList;
begin
  AList := FCSList.LockList;
  for I := AList.Count - 1 downto 0 do
    TCriticalSection(AList[I]).Free;
  FCSList.Free;
  FNameList.Free;
  inherited;
end;


function TCriticalSectionList.GetByName(AName: string): TCriticalSection;
var
  AList: TList;
  AIdx: Integer;
begin
  AList := FCSList.LockList;
  try
    AName := UpperCase(AName);
    AIdx := FNameList.IndexOf(AName);
    if AIdx < 0 then
    begin
      FNameList.Add(AName);
      Result := TCriticalSection.Create;
      AList.Add(Result);
    end
    else
      Result := AList[AIdx];
  finally
    FCSList.UnlockList;
  end;
end;

var
  _CSList: TCriticalSectionList;

function CSList: TCriticalSectionList;
begin
  if not Assigned(_CSList) then
    _CSList := TCriticalSectionList.Create;
  Result := _CSList;
end;

initialization
  _CSList := nil;
finalization
  _CSList.Free;
end.

该类基本上定义了一个关键部分列表,可通过“名称”访问。当您第一次请求特定名称的关键部分时,该关键部分会自动为您创建。您必须访问此类的单个实例,使用提供的 CSList 函数。

当列表的实例被销毁时,所有临界区都被销毁,例如,“默认”实例在应用程序结束时被销毁。

你可以像下面这样写代码:

begin
  CSList[fDBTable].Enter;
  try
    DoStuff;
  finally
    CSList[fDBTable].Leave;
  end;
end;

享受吧。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-24
    • 1970-01-01
    • 1970-01-01
    • 2021-12-09
    • 1970-01-01
    • 1970-01-01
    • 2018-10-17
    • 2013-03-26
    相关资源
    最近更新 更多