【问题标题】:Delphi, secure and fast access to partial elements of a static arrayDelphi,安全快速地访问静态数组的部分元素
【发布时间】:2013-04-25 20:23:54
【问题描述】:

我有 100 个记录类型的静态数组:

TMy_Array:array[1..100] of T;

其中 T 是:

T = record
  A: double;
  B: Date;
  C: String;
end;

我有n 类似的线程在修改TMy_Array 中的元素

TMy_Array[n].A; 
TMy_Array[n].B; 
TMy_Array[n].C)`. 

N 接近于100

我还有 10 个其他线程可以选择性地修改任何字段 TMy_Array。 为了安全地实现这一点,我应该使用关键部分吗?问题来了:

是否只使用一个临界区不会导致争用和等待访问数组的线程过度拥挤?

也许我可以或应该应用 100 个关键部分来保护对 T_Array 中特定项目的访问并且不会阻塞其他线程?

Var
  Cs:array [1..100] of TRTLCriticalSection;

//then for n-thread:
EnterCriticalSection(CS[n]);
TMy_Array[n].A:=…;
TMy_Array[n].B:=…;
TMy_Array[n].C:=…;
LeaveCriticalSection(CS[n]);

这是个好主意,不会使应用程序过载吗?

第二个问题:

TDateTime(真的是Double)和String 是原子数据吗?我可以读取(仅读取)它们而不用担心在另一个线程正在写入它们时发生冲突吗?

【问题讨论】:

  • 你没有提到你的 Delphi 版本。后来的(D2009)版本有TThreadList,它具有列表的内置线程锁定。我建议首先尝试这是否足以满足您的应用程序。在任何给定时间,只有一个线程可以对该列表进行操作。
  • 关于原子操作。从这里引用@dThorpe:Delphi: preferred way of protection using Critical Sections。 “如果您具有多值语义(需要在同一操作中更新多条数据)并且您的设计要求所有值始终相互一致,那么原子写入毫无意义。”
  • @LURD: TThreadList 已经存在了很长时间,至少从 D5 开始,可能甚至更早。
  • @Artik:如果您需要多个线程同时访问相同 T 实例,那么是的,您需要实现某种锁定机制。整个阵列的单个锁可能是一个瓶颈,但每个T 上的锁也可能是矫枉过正。这实际上取决于您实际尝试使用线程来完成什么。
  • @RemyLebeau,很可能就是这样。我只看到它位于Generics.Collections 中,但现在我看到它也在System.Classes 中。很抱歉造成混乱。

标签: delphi thread-safety critical-section


【解决方案1】:

尝试serialize 访问您的列表/数组。

  • 最简单的序列化方法是使用TThreadList 来保存记录。 TThreadList 有两个版本,Generics.Collections 中的一个泛型和Classes 中的非泛型。对此类列表的所有访问都受到锁定/解锁机制的保护。 这种方法是一个好的开始。测量性能,看看是否有问题瓶颈。

  • 另一种方法是让一个线程通过线程安全队列保护所有列表/数组访问。 其他试图从列表/数组中读取/写入数据的线程在队列上发送读取/写入请求。

    • 对于读取请求,记录的副本将在另一个队列中发送到请求线程。
    • 写请求由守护线程提交。

    现在一切都是以最小延迟的事件驱动的。没有关于线程安全的冲突和对因果关系的清晰描述。

    对于线程安全队列,如果您有 Delphi-XE2 或更新版本,请查看 TThreadedQueue


这是一个概述上述队列方法的示例。

Uses
  Classes,SysUtils,Generics.Collections;
Type
  T = record
    A : Double;
    B : String;
  end;
var
  MyArr : array[1..100] of T;
  GuardingQueue : TThreadedQueue<TProc>;

procedure GuardingThread.Execute;
var
  aProc : TProc;
begin
  while not Terminated do
  begin
    aProc := GuardingQueue.PopItem;
    if not Assigned(aProc) then
      Exit; // Quit thread when nil is sent to the queue
    aProc(); // Execute request
  end;
end;

procedure AccessingThread.Execute;
var
  aLocalQueue : TThreadedQueue<T>;
  aT : T;
begin
  // Make sure aLocalQueue is initialized
  // To get data fom the array ...
  GuardingQueue.PushItem( // read from array
    procedure
    var
      aT : T;
    begin
      aT.A := MyArr[2].A;
      aT.B := MyArr[2].B;
      aLocalQueue.PushItem(aT);
    end
  );
  aT := aLocalQueue.PopItem; // Here is the result from the read request

  // Writing to the array ...
  GuardingQueue.PushItem( // write to array
    procedure
    begin
      MyArr[2].A := 2;
      MyArr[2].B := 'Test';
    end
  );

end;

【讨论】:

  • 谢谢,看来它解决了我的问题。我会尝试使用它。不幸的是,我有 Delphi XE,但我查看了文档 - 也许它有 TThreadedQueue。再次感谢您。
  • 不幸的是,Delphi XE 中的TThreadedQueueTMonitor 中的几个错误而损坏。见TThreadedQueue not capable of multiple consumers?
猜你喜欢
  • 2010-10-16
  • 1970-01-01
  • 2018-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-01
相关资源
最近更新 更多