【问题标题】:The best practice on how to wait for data within thread and write it into file?如何在线程中等待数据并将其写入文件的最佳实践?
【发布时间】:2012-03-19 20:57:24
【问题描述】:

我正在编写多线程应用程序。我有两个线程。 On 用于将一些数据从设备传输到全局数据缓冲区,其次用于将这些数据写入文件。

从设备到缓冲区的数据正在异步传输。第二个线程的目的应该是等待指定数量的数据写入主数据缓冲区,最后写入文件。

第一个线程在 DLL 中,第二个在主应用程序中。我暂时用事件来解决这个问题。第一个线程将数据从设备传输到主数据缓冲区并计算数据,当传输指定数量的数据时,它会设置一个事件。第二个等待事件发出信号,然后运行一些数据存储代码。很简单,因为它正在工作。

  Thread1.Execute:

  var DataCount, TransferedData: Integer;

  DataCounter := 0;
  while not Terminted do
  begin
    TransferData(@pData, TransferedData);
    Inc(DataCounter, TransferedData)
    if DataCounter >= DataCountToNotify then SetEvent(hDataCount);
  end;



  Thread2.Execute:

  hndlArr[0] := hDataCount;
  hndlArr[1] := hTerminateEvent;

  while (not Terminated) do
  begin
    wRes := WaitForMultipleObjects(HandlesCount, Addr(hndlArr), false, 60000);
    case wRes of
      WAIT_OBJECT_0:
        begin
          Synchronize(WriteBuffer);                   // call it from main thread
          ResetEvent(hndlArr[0]);
        end;
      WAIT_OBJECT_0 + 1:
        begin
          ResetEvent(hTerminateEvent);
          break;
        end;
      WAIT_TIMEOUT:  Break;
    end;
  end;

现在我想让第二个线程更加独立......所以我可以创建第二个线程的多个实例,而我不需要等待第一个线程。我想将代码的数据计数部分从第一个线程移动到第二个线程,这样我就不再需要在第一个线程中进行数据计数了。第一个将仅用于数据传输目的。

我想使用第二个作为数据计数器并存储数据。但现在我将不得不循环并不断手动检查指定的数据量。如果我有while循环,我将不得不添加一些睡眠,所以第二个线程不会降低计算机性能,但我不知道睡眠应该多长时间,而第一个线程中的数据传输速度不是恒定的,因此第二个线程中的计数速度会有所不同。

我猜这个代码示例不好:

  Thread2.Execute:
  var DataCount: Integer;
  DataIdx1 := GetCurrentDataIdx; 
  while (not Terminated) do
  begin     
    if (GetCurrentDataIdx - DataIdx1) >= DataCountToNotify then 
    begin
      Synchronize(WriteBuffer);
      DataIdx1 := GetCurrentIdx;
    end;
    sleep(???);
  end;

所以我的问题是解决数据计数问题并将其存储在第二个线程中的最佳方法是什么?您有什么经验和建议?

【问题讨论】:

  • 为什么要强制将文件写入主线程Synchronize(WriteBuffer)?我不明白你将如何设法增加第二个线程并仍然保持对单个文件的写入顺序。
  • 线程将成倍增加,但数据将存储到不同的文件中。我无处写我要把它写到同一个文件中。我的目标是拥有多个控件,它们能够将来自同一源的数据存储到不同的文件中。
  • 很高兴知道,因为答案将取决于此信息。我仍然想知道为什么必须将文件写入强制执行到主线程。
  • @Nix - 好好听 LU RD。文件系统是 100% 线程安全的,不需要同步。
  • Nix,第一个线程如何知道如何将数据分发给接收线程?通常这样的生产者线程只是组装数据并将其放入队列中。然后根据需要尽可能多的消费者线程可以处理队列的耗尽。

标签: multithreading delphi thread-safety delphi-2010


【解决方案1】:

你有一些问题。 @LU RD 已经指出了一个 - 不要同步不需要同步的东西。目前尚不清楚“WriteBuffer”的作用,但我使用的文件系统和所有数据库都可以让一个线程打开文件/表并写入它们。

您的缓冲系统可能需要一些注意。是否有一些“指定的数据量”或者这是一些允许懒惰写入的概念数字?

通常,生产者和消费者线程在队列上交换多个缓冲区指针,因此避免共享任何单个缓冲区。鉴于这是一个 DLL,因此内存管理调用可能会出现问题,我可能也会通过在启动时创建一个缓冲区池来在系统周围传输数据来避免它们。我会使用缓冲区类,而不仅仅是指向内存的指针,但这不是绝对必需的(只是更容易/灵活/安全)。

Sleep() 循环是一种非常糟糕的线程间通信方式。 Sleep() 确实有它的用途,但这不是其中之一。 Delphi/Windows 有大量的同步机制——事件、信号量、互斥体等——它们使得这种轮询变得不必要。

LU RD 还提到了必须保持顺序的数据的并行处理问题。这通常需要另一个线程,一个列表样式的集合和序列号。在线程间通信正常运行之前,我不会尝试这样做。

【讨论】:

  • 上层代码仅用于演示目的。不要打扰同步。我已经有固定的东西,我使用适当的机制来同步线程等。正如我提到的,我想计算数据,然后当条件适用于例如。 DataCountToNotify = 8192 我想存储数据。但我想避免睡觉。有没有其他方法可以做到这一点?
  • @Nix 如果我没记错的话,只要您的线程在 while 循环中执行“某事”,那么您可以跳过“睡眠”,但您可以使用 sleep(1),即睡眠时间在 1 到 55 毫秒之间(win xp 有问题,不确定未来的版本)
  • @DorinDuminica,有关反对在线程中使用 Sleep 的论据,请参阅 thread-sleep-is-a-sign-of-a-poorly-designed-program
  • @LURD 感谢您提供的链接,它提供了一些非常好的信息,但是,睡眠可用于说重新建立连接或“等待某个地方可用 X 量数据(表, 文件系统等)”
【解决方案2】:

如果您想避免在第二个线程中调用 Sleep(),请使用像 TSimpleEvent 这样的可等待计时器。

设置睡眠时间以处理所有时间条件。 使用这种方案而不是普通的Sleep() 有一个优势,因为等待计时器不会使线程进入深度睡眠。

要释放线程,请参阅代码中的 cmets。

var
  FEvent: TSimpleEvent;
  FSleepTime: Integer = 100;  // Short enough to handle all cases

Constructor TThread2.Create;
begin
  Inherited Create( False);
  FEvent := TSimpleEvent.Create;
  Self.FreeOnTerminate := True;
end;

procedure TThread2.Execute;
var 
  DataCount: Integer;
begin
  DataIdx1 := GetCurrentDataIdx; 
  while (fEvent.WaitFor(FSleepTime) = wrTimeout) do
  begin
    if Terminated then
      break;
    // Do your work
    if (GetCurrentDataIdx - DataIdx1) >= DataCountToNotify then 
    begin
      // Write data to buffer
      DataIdx1 := GetCurrentIdx;
    end;
  end;
end;

// To stop the thread gracefully, call this instead of Terminate or override the DoTerminate
procedure TThread2.SetTerminateFlag;
begin
  FEvent.SetEvent;
end;

【讨论】:

  • LU RD 感谢这看起来很有希望。我将不得不根据第一个线程中数据传输的速度来改变 FSleep 时间。我可以计算出那个时间。就像我说的那样,我尽量避免在线程中睡眠,这就是为什么我在寻找更好的睡眠替代方案。我从未在我编程的任何线程中使用过睡眠。我发现它不是很安全。我喜欢系统更加坚定。非常感谢。我会尝试按照您建议的方式实现它。
  • 乍一看这是合理的,但事实并非如此。您的意思是“wrSignaled”而不是“wrTimeout”吗?此外,这里仍然有 Sycnhronize 调用,这使得线程比无效更糟糕 - 它实际上会引入更多延迟。
  • @MartinJames,同步显然是错误的,但 OP 说这不再是问题了。循环将在未发出信号时继续运行(每 FSleepTime 毫秒)。在我看来这看起来不错。
  • 我只是想补充一点,这个解决方案可能有一个潜在的缺陷:线程的第一次迭代将以FSleepTime 定义的延迟执行。这可以通过将循环从 while 更改为 repeat..until 来避免。
猜你喜欢
  • 1970-01-01
  • 2014-12-29
  • 2013-02-02
  • 1970-01-01
  • 2021-12-14
  • 1970-01-01
  • 2021-06-17
  • 2010-10-14
  • 1970-01-01
相关资源
最近更新 更多