我什么时候应该使用关键部分?

pop*_*p32 5 delphi multithreading synchronization critical-section

这是交易.我的应用程序有很多线程做同样的事情 - 从大文件(> 2gb)读取特定数据,解析数据并最终写入该文件.

问题是,有时可能会发生一个线程从文件A读取X并且第二个线程写入同一文件A的X.会出现问题?

I/O代码对每个文件使用TFileStream.我将I/O代码拆分为本地(静态类),因为我担心会出现问题.由于它是分裂的,应该有关键的部分.

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

情况1:

procedure Foo(obj:TObject);
begin ... end;
Run Code Online (Sandbox Code Playgroud)

案例2:

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

案例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;
Run Code Online (Sandbox Code Playgroud)

问题1:在哪种情况下我需要关键部分,以便在> 1个线程同时调用它时没有问题?

问题2:如果线程1从文件A读取X(条目)而线程2写入X(条目)到文件A,是否会出现问题?

我什么时候应该使用关键部分?我试着把它想象成我的脑袋,但它很难 - 只有一个线程:))

编辑

这适合它吗?

{每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;
Run Code Online (Sandbox Code Playgroud)

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

案例1:相同的TSpecificFile

案例2:不同的TSpecificFile?

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

Dis*_*ned 7

通常,每当多个线程可以同时访问共享资源时,您需要一个锁定机制(关键部分是锁定机制),并且至少有一个线程将写入/修改共享资源.
无论资源是内存中的对象还是磁盘上的文件,都是如此.
并且锁定是必要的原因在于,如果读取操作与写入操作同时发生,则读取操作可能获得导致不可预测行为的不一致数据.
Stephen Cheung在文件处理方面提到了平台特定的考虑因素,我在此不再赘述.

作为旁注,我想强调可能适用于您的情况的另一个并发问题.

  • 假设一个线程读取一些数据并开始处理.
  • 然后另一个线程做同样的事情.
  • 两个线程都确定它们必须将结果写入文件A的位置X.
  • 最好写入的值是相同的,其中一个线程实际上只是浪费时间.
  • 最坏的情况是,其中一个线程的计算被覆盖,结果就会丢失.

您需要确定这是否会对您的应用程序造成问题.我必须指出,如果是这样,只是锁定读写操作将无法解决它.此外,试图延长锁的持续时间会导致其他问题.

选项

关键部分

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

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

我还将建议您在解决方案中考虑其他两种工具.

单线程

多么令人震惊的事情!但是说真的,如果你去多线程的理由是"让应用程序更快",那么你就出于错误的原因去了多线程.大多数这样做的人实际上最终制作他们的应用程序,更难写,更不可靠,更慢!

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

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

排队操作

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

而不是线程1:

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

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

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

  • +单线程`后面的推理+1.然而,当前的I/O系统可以轻松地推动100 Mb/s,并且线程可以一次读取数十兆字节的数据,从而限制了对更可忍受的东西的破坏.或者可以实现生产者 - 消费者算法,其中只有一个线程执行I/O并且多个线程执行解析和处理. (2认同)

Ste*_*ung 5

只有在多个代理程序正在执行某些操作时才会导致问题(或错误)的共享数据需要同步.

显然,只有当你不希望其他编写器进程在写入完成之前对新数据进行践踏时,文件写入操作才应该包含在该文件的关键部分 - 如果你有一半文件可能不再一致由另一个进程修改的新数据没有看到新数据的另一半(尚未由原始编写者进程写出).因此,您将拥有一个CS集合,每个文件一个.当你完成写作时,应该尽快释放CS.

在某些情况下,例如内存映射文件或稀疏文件,O/S 可能允许您同时写入文件的不同部分.因此,在这种情况下,您的CS必须位于文件的特定.因此,每个文件都有一个CS集合(每个段一个).

如果您写入文件并同时读取它,则读者可能会得到不一致的数据.在某些操作系统中,允许读取与写入同时发生(可能读取来自缓存的缓冲区).但是,如果您正在写入文件并同时阅读它,则您阅读的内容可能不正确.如果您需要有关读取的一致数据,那么读者也应该遵守关键部分.

在某些情况下,如果您正在写一个段并从另一个段读取,则O/S可能允许它.但是,这是否会返回正确的数据通常无法保证,因为您无法始终确定文件的两个段是否可能驻留在一个磁盘扇区或其他低级O/S内容中.

因此,一般来说,建议是在每个文件的CS中包装任何文件操作.

从理论上讲,您应该能够同时从同一个文件中读取,但将其锁定在CS中只能允许一个读者.在这种情况下,您需要将实现分为"读锁"和"写锁"(类似于数据库系统).这是非常重要的,因为你必须处理促进不同级别的锁定.

注意事项:您尝试数据的方式(在段中同时读取和写入GB大小的数据集)通常在数据库中完成.您应该考虑将数据文件分解为数据库记录.否则,您要么因锁定而遭受非优化的读/写性能,要么最终重新发明关系数据库.