Лучшая практика о том, как ждать данных в потоке и записать их в файл? - PullRequest
5 голосов
/ 20 марта 2012

Я программирую многопоточное приложение. У меня есть две темы. 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;

Итак, мой вопрос: каков наилучший подход к решению этой проблемы с подсчетом данных и хранением их во втором потоке? Каковы ваши впечатления и предложения?

Ответы [ 2 ]

5 голосов
/ 20 марта 2012

У вас есть некоторые проблемы. @LU RD уже указал один - не синхронизируйте вещи, которые не нужно синхронизировать. Непонятно, что делает «WriteBuffer», но файловая система и все базы данных, которые я использовал, просто прекрасно, когда один поток открывает файл / таблицу и записывает в них.

Ваша буферная система, вероятно, может сделать с некоторым вниманием. Есть ли какой-то «определенный объем данных» или это некая условная цифра, которая позволяет лениво писать?

Как правило, потоки производителя и потребителя обмениваются несколькими указателями буфера в очередях и поэтому избегают совместного использования любого отдельного буфера. Учитывая, что это DLL и поэтому вызовы управления памятью могут быть проблематичными, я бы, вероятно, избежал их, создав при запуске пул буферов для передачи данных по системе. Я бы использовал буферный класс, а не просто указатели на память, но это не обязательно (просто гораздо проще / гибче / безопаснее).

Циклы Sleep () являются чрезвычайно плохим способом связи между потоками. Sleep () использует его, но это не один из них. В Delphi / Windows имеется множество механизмов синхронизации - событий, семафоров, мьютексов и т. Д., Что делает такой опрос ненужным.

LU RD также упомянул проблемы параллельной обработки данных, порядок которых должен быть сохранен. Это часто требует еще одного потока, коллекции в стиле списка и порядковых номеров. Я бы не стал этого делать, пока у вас не заработают хорошие связи между потоками.

2 голосов
/ 20 марта 2012

Если вы хотите избежать вызова Sleep() во втором потоке, используйте ожидаемый таймер, например TSimpleEvent.

Установите время ожидания для обработки всех ваших временных условий. Преимущество использования этой схемы вместо обычной Sleep(), поскольку ожидаемый таймер не переведет поток в глубокий сон.

Чтобы избавиться от темы, см. Комментарии в коде.

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;
...