Приостановить поток менее чем на одну миллисекунду - PullRequest
14 голосов
/ 14 января 2012

В тестовом приложении клиента обмена сообщениями поток производителя должен быть ограничен во избежание переполнения сервера.

Поскольку скорость передачи составляет около 25 000 сообщений в секунду (40 микросекунд на сообщение), задержка, вызванная Sleep (1), будет слишком большой.

Как заставить поток спать менее чем за миллисекунду в Windows содержит некоторую информацию, относящуюся к Windows API. Есть ли фрагмент кода, класс или библиотека для Delphi?


После ответа Бенса я обнаружил, что Sleep с различными значениями ниже 15 также дает разные скорости передачи (Windows Vista):

Sleep (1) после каждых 20 сообщений:

00:02 tx/rx 25740/3705 12831/1846 msgs/sec (77/541 microsecs/msg)
00:04 tx/rx 53101/7405 13255/1848 msgs/sec (75/541 microsecs/msg)
00:06 tx/rx 79640/11288 13260/1879 msgs/sec (75/532 microsecs/msg)
00:08 tx/rx 104520/14562 13055/1818 msgs/sec (76/550 microsecs/msg)
00:10 tx/rx 130760/18829 13066/1881 msgs/sec (76/531 microsecs/msg)

Sleep (5) после каждых 20 сообщений:

00:02 tx/rx 7640/3622 3812/1807 msgs/sec (262/553 microsecs/msg)
00:04 tx/rx 14660/10794 3661/2695 msgs/sec (273/371 microsecs/msg)
00:06 tx/rx 21480/18171 3577/3026 msgs/sec (279/330 microsecs/msg)
00:08 tx/rx 28140/25642 3515/3203 msgs/sec (284/312 microsecs/msg)
00:10 tx/rx 34980/32692 3496/3267 msgs/sec (286/306 microsecs/msg)

Это было неожиданно после прочтения комментария о нижнем пределе для занятого ожидания

А значения без регулирования

00:02 tx/rx 44065/494 21988/246 msgs/sec (45/4065 microsecs/msg)
00:04 tx/rx 90493/756 22595/188 msgs/sec (44/5319 microsecs/msg)
00:06 tx/rx 142982/907 23810/151 msgs/sec (41/6622 microsecs/msg)
00:08 tx/rx 192562/1144 24055/142 msgs/sec (41/7042 microsecs/msg)
00:10 tx/rx 237294/1395 23717/139 msgs/sec (42/7194 microsecs/msg)

Ответы [ 6 ]

21 голосов
/ 14 января 2012

Отправить 20 сообщений, а затем спать в течение 1 миллисекунды?

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

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

Итак, проверяйте источник точных часов каждый раз, когда вы просыпаетесь, и рассчитывайте, сколько сообщений отправлять на основе прошедшего времени, не используйте постоянную 20.

12 голосов
/ 14 января 2012

1) Получить текущее время.

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

3) Отправьте столько сообщений.

4) Спите за наименьшее количество, которое вы можете.

5) Перейти к шагу 1.

3 голосов
/ 14 января 2012

Как уже говорили другие, вы не можете спать в течение короткого промежутка времени (и даже сон (1) ненадежен - он может легко спать гораздо дольше 1 мс).

Лучше всего рассчитать время для следующего сообщения, а затем выполнить занятое ожидание - цикл и время проверки - до наступления нужного времени. Ниже представлено полное решение вместе с небольшим тестовым фреймворком.

program SendEquidistantMessages;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Windows, SysUtils;

procedure SendMessage(msgNum: integer);
begin
  // send the message here
end;

procedure WaitUntil(nextMsgTime: int64);
var
  currTime: int64;
begin
  repeat
    QueryPerformanceCounter(currTime);
    if currTime >= nextMsgTime then
      break; //repeat
    asm pause; end;
  until false;
end;

procedure SendMessages(numMsg, msgPerSec: integer);
var
  iMsg       : integer;
  nextMsgTime: int64;
  perfFreq   : int64;
  prevMsg    : int64;
  startTime  : int64;

begin
  Assert(QueryPerformanceFrequency(perfFreq));
  Assert(QueryPerformanceCounter(startTime));
  for iMsg := 1 to numMsg do begin
    WaitUntil(Round(startTime + iMsg/msgPerSec * perfFreq));
    SendMessage(iMsg);
  end;
end;

var
  time: cardinal;

begin
  try
    time := GetTickCount;
    SendMessages(20000, 5000);
    time := GetTickCount-time;
    Writeln('20.000 messages sent in ', time/1000:4:1, ' sec; ',
      'required rate = 5000 msg/sec, real rate = ', 20000/(time/1000):6:1, ' msg/sec');
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
0 голосов
/ 04 февраля 2016

Обходной путь - получить текущее время с нано секундами и сложить его со временем, в течение которого вы хотите спать (ждать) и зацикливаться, пока текущее время (с нано секундами) не станет больше, чем вычисленное вами время, есть методкоторые иллюстрируют это:

public void wait(long nano){
    long t= System.nanoTime();
    t+= nano;
    while(System.nanoTime()<t);
}

Обратите внимание, что: 1 секунда = 1000 миллисекунд = 1000000 микросекунд = 1000000000 наносекунд

так

1 миллисекунда = 1000000 наносекунд

0 голосов
/ 12 июля 2012

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

Более подробную информацию также можно найти в проекте отметки времени Windows

0 голосов
/ 14 января 2012

Вместо того, чтобы спать, почему бы не использовать TTheard.Yield , чтобы позволить другому потоку / процессу иметь bash на процессоре?

...