Вопрос

Я написал многопоточное приложение для Windows, где поток:
   A & # 8211; форма окна, которая обрабатывает взаимодействие с пользователем и обрабатывает данные из B.
   B & # 8211; иногда генерирует данные и передает их два А.

Потокобезопасная очередь используется для передачи данных из потока B в A. Функции enqueue и dequeue охраняются с использованием объектов критического раздела Windows.

Если при вызове функции enqueue очередь пуста, функция будет использовать PostMessage, чтобы сообщить A, что в очереди есть данные. Функция проверяет, чтобы убедиться, что вызов PostMessage выполнен успешно, и многократно вызывает PostMessage, если он не был успешным (PostMessage еще не удалось).

Это работало довольно долго, пока один конкретный компьютер не начал терять случайное сообщение. Под потерей я подразумеваю, что PostMessage успешно возвращается в B, но A никогда не получает сообщение. Это заставляет программное обеспечение казаться замороженным.

Я уже придумал пару приемлемых обходных путей. Мне интересно знать, почему Windows теряет эти сообщения и почему это происходит только на одном компьютере.

Вот соответствующие части кода.

// Only called by B
procedure TSharedQueue.Enqueue(AItem: TSQItem);
var
 B: boolean;
begin
  EnterCriticalSection(FQueueLock);
  if FCount > 0 then
    begin
      FLast.FNext := AItem;
      FLast := AItem;
    end
  else
    begin
      FFirst := AItem;
      FLast := AItem;
    end;

  if (FCount = 0) or (FCount mod 10 = 0) then // just in case a message is lost
    repeat
      B := PostMessage(FConsumer, SQ_HAS_DATA, 0, 0);
      if not B then 
  Sleep(1000); // this line of code has never been reached
    until B;

  Inc(FCount);
  LeaveCriticalSection(FQueueLock);
end;

// Only called by A 
function TSharedQueue.Dequeue: TSQItem;
begin
  EnterCriticalSection(FQueueLock);
  if FCount > 0 then
    begin
      Result := FFirst;
      FFirst := FFirst.FNext;
      Result.FNext := nil;
      Dec(FCount);
    end
  else
    Result := nil;
  LeaveCriticalSection(FQueueLock);
end;

// procedure called when SQ_HAS_DATA is received
procedure TfrmMonitor.SQHasData(var AMessage: TMessage);
var
  Item: TSQItem;
begin
  while FMessageQueue.Count > 0 do
    begin
      Item := FMessageQueue.Dequeue;
      // use the Item somehow
    end;
end;
Это было полезно?

Решение

Защищен ли FCount также FQueueLock ? Если нет, то ваша проблема заключается в увеличении FCount после того, как опубликованное сообщение уже обработано.

Вот что может происходить:

<Ол>
  • B входит в критическую секцию
  • B вызывает PostMessage
  • A получает сообщение, но ничего не делает, поскольку FCount равен 0
  • B увеличивает FCount
  • B покидает критическую секцию
  • А сидит там как утка
  • Быстрое решение - увеличить FCount перед вызовом PostMessage .

    Имейте в виду, что все может произойти быстрее, чем можно было ожидать (т. е. сообщение, отправленное с PostMessage, было перехвачено и обработано другим потоком до того, как вы сможете увеличить FCount несколькими строками позже), особенно когда вы находитесь в истинная многопоточная среда (несколько процессоров). Вот почему я спросил ранее, «проблемный ли компьютер»? было несколько процессоров / ядер.

    Простой способ устранения проблем, подобных этим, состоит в том, чтобы создать код с дополнительной регистрацией для регистрации при каждом вводе метода, входа / выхода из критического раздела и т. д. Затем вы можете проанализировать журнал, чтобы увидеть истинный порядок событий.

    Отдельно отметим, что небольшая оптимизация, которую можно выполнить в сценарии «продюсер / потребитель», например, состоит в том, чтобы использовать две очереди вместо одной. Когда потребитель просыпается, чтобы обработать полную очередь, вы меняете полную очередь на пустую и просто блокируете / обрабатываете полную очередь, в то время как новая пустая очередь может быть заполнена без двух потоков, пытающихся заблокировать очереди друг друга. Тем не менее, вам все равно понадобится некоторая блокировка в обмене двумя очередями.

    Другие советы

      

    Если очередь пуста при постановке в очередь   функция вызывается, функция будет   используйте PostMessage, чтобы сказать A, что там   это данные в очереди.

    Вы блокируете очередь сообщений перед проверкой размера очереди и выдачей PostMessage ? Возможно, вы испытываете состояние гонки, когда вы проверяете очередь и обнаруживаете ее непустой, когда фактически A обрабатывает самое последнее сообщение и собирается бездействовать.

    Чтобы узнать, действительно ли у вас есть состояние гонки, а не проблема с PostMessage , вы можете переключиться на использование события. Рабочий поток (A) будет ожидать события, а не ждать сообщения. B просто установит это событие вместо публикации сообщения.

      

    Это работало довольно долго   пока один конкретный компьютер не начал   потерять случайное сообщение.

    Не случайно ли количество процессоров или ядер, которые имеет данный компьютер, отличается от других, где вы не видите проблем? Иногда при переключении с компьютера с одним процессором на компьютер с более чем одним физическим процессором / ядром могут возникать новые условия гонки или взаимоблокировки.

    Может ли быть второй экземпляр, который по незнанию работает и ест сообщения, помечая их как обработанные?

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top