multithreading - 我什么时候应该使用临界区?

标签 multithreading delphi synchronization critical-section

这是交易。我的应用程序有很多线程执行相同的操作 - 从大文件(> 2GB)中读取特定数据,解析数据并最终写入该文件。

问题是,有时可能会发生一个线程从文件 A 中读取 X,而第二个线程写入同一文件 A 的 X 的情况。会出现问题吗?

I/O 代码对每个文件使用 TFileStream。我将I/O代码拆分为本地(静态类),因为我担心会有问题。既然是拆分的,就应该有关键部分。

下面的每个案例都是未实例化的本地(静态)代码。

案例1:

procedure Foo(obj:TObject);
begin ... end;

案例2:

procedure Bar(obj:TObject);
var i: integer;
begin
  for i:=0 to X do ...{something}
end;

案例3:

function Foo(obj:TObject; j:Integer):TSomeObject
var i:integer;
begin
  for i:=0 to X do
    for j:=0 to Y do
      Result:={something}
end;

问题 1:在什么情况下我需要临界区,这样如果 >1 个线程同时调用它就不会出现问题?

问题2:如果线程1从文件A读取X(entry),而线程2向文件A写入X(entry),会出现问题吗?

什么时候应该使用临界区?我试着用我的头脑想象它,但这很难——只有一根线:))

编辑

这适合吗?

{每个 2GB 文件一个类}

TSpecificFile = class
  cs: TCriticalSection;
  ...
end;

TFileParser = class
  file :TSpecificFile;
  void Parsethis; void ParseThat....
end;

function Read(file: TSpecificFile): TSomeObject;
begin
  file.cs.Enter;
  try
    ...//read
  finally
    file.cs.Leave;
  end;
end;

function Write(file: TSpecificFile): TSomeObject;
begin
  file.cs.Enter;
  try
    //write
  finally
    file.cs.Leave
  end;
end;

现在如果两个线程调用 Read 会出现问题:

情况1:相同的TSpecificFile

情况2:不同的TSpecificFile?

我需要另一个关键部分吗?

最佳答案

一般来说,只要多个线程可以同时访问共享资源,并且至少有一个线程会写入/修改共享资源,就需要一种锁定机制(临界区是一种锁定机制)。< br/> 无论资源是内存中的对象还是磁盘上的文件,都是如此。
之所以需要加锁,是因为如果读操作与写操作同时发生,则读操作很可能获得不一致的数据,从而导致不可预测的行为。
Stephen Cheung 提到了有关文件处理的平台特定注意事项,我在此不再重复。

As a side note, I'd like to highlight another concurrency concern that may be applicable in your case.

  • Suppose one thread reads some data and starts processing.
  • Then another thread does the same.
  • Both threads determine that they must write a result to position X of File A.
  • At best the values to be written are the same, and one of the threads effectively did nothing but waste time.
  • At worst, the calculation of one of the threads is overwritten, and the result is lost.

You need to determine whether this would be a problem for your application. And I must point out that if it is, just locking the read and write operations will not solve it. Furthermore, trying to extend the duration of the locks leads to other problems.

选项

关键部分

是的,您可以使用关键部分。

  • 您需要选择关键部分的最佳粒度:每个整个文件一个,或者可能使用它们来指定文件中的特定 block 。
  • 该决定需要更好地了解您的应用程序的用途,因此我不会为您回答。
  • 请注意死锁的可能性:
    • 线程1获取锁A
    • 线程2获取锁B
    • 线程 1 需要锁 B,但必须等待
    • 线程 2 需要锁 A - 导致死锁,因为两个线程都无法释放其获取的锁。

我还将建议您在解决方案中考虑另外 2 个工具。

单线程

这句话说得多么令人震惊!但说实话,如果您使用多线程的原因是“让应用程序更快”,那么您使用多线程的原因就错误。大多数这样做的人实际上最终会让他们的应用程序变得更难编写、更不可靠并且速度更慢!

认为多线程可以加速应用程序是一个非常常见的误解。如果一个任务需要 X 个时钟周期来执行 - 它将需要 X 个时钟周期!多个线程不会加速任务,它允许并行完成多个任务。但这可能是一件坏事! ...

您将您的应用程序描述为高度依赖于从磁盘读取、解析读取的内容以及写入磁盘。根据解析步骤的 CPU 密集程度,您可能会发现所有线程都将大部分时间用于等待磁盘 IO 操作。在这种情况下,多个线程通常仅用于将磁盘头分流到(嗯圆形)磁盘盘片的远“角”。磁盘 IO 仍然是瓶颈,线程使其表现得好像文件最大程度地碎片化一样。

排队操作

假设您采用多线程的原因是有效的,并且您仍然有线程在共享资源上运行。您可以将共享资源操作排队到特定线程上,而不是使用锁来避免并发问题。

所以代替线程 1:

  • 从文件 A 中读取位置 X
  • 解析数据
  • 写入文件 A 中的位置 Y

创建另一个线程; FileA 线程:

  • FileA 有一个指令队列
  • 当它到达读取位置 X 的指令时,它就会这样做。
  • 它将数据发送到线程 1
  • 线程 1 解析其数据 --- 而 FileA 线程继续处理指令
  • 线程 1 放置一条指令,将其结果写入 FileA 线程队列末尾的位置 Y --- 同时 FileA 线程继续处理其他指令。
  • 最终FileA线程将按照Trhead 1的要求写入数据。

关于multithreading - 我什么时候应该使用临界区?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5361127/

相关文章:

delphi - 重载运算符和转换类型

rest - TJSON.JsonToObject 不通过 setter

javascript - 我如何迭代数组并将项目传递给方法并且所有方法都应该同步运行?

android - NDK分辨率结果:项目设置:Gradle模型版本= 5.4.1,NDK版本为UNKNOWN错误

java - Thread.State : WAITING (parking) vs BLOCKED at sun. misc.Unsafe.park() 有什么区别

objective-c - 如何在macOS的非主线程上发生事件循环?

java - 多个数据库并行查询,用于单个客户端请求

ios - NSOperation 阻止 UI 绘制?

delphi - 窗口框架所需的 WM_NCLBUTTONUP 不会触发。请问最简单的 Delphi 修复是什么?

javascript - Promise.all 没有按预期工作