Приостановить поток менее чем на одну миллисекунду

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

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

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


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

Сон (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)

Сон (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)

person mjn    schedule 14.01.2012    source источник
comment
Вы можете реализовать только ожидание занятости менее 1 мс, но не спящий режим.   -  person kludg    schedule 14.01.2012
comment
@Serg, AFAIK около 15 мс   -  person OnTheFly    schedule 14.01.2012


Ответы (6)


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

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

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

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

person Ben Voigt    schedule 14.01.2012
comment
В Windows я рекомендую QueryPerformanceCounter (msdn.microsoft.com/en-us/library/windows/desktop/) в качестве источника точных часов (с разрешением около половины микросекунды). - person Crashworks; 14.01.2012
comment
+1, определенно лучший подход - избавьтесь от необходимости короткой задержки. - person Martin James; 14.01.2012

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

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

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

4) Спите как можно меньше.

5) Переходите к шагу 1.

person David Schwartz    schedule 14.01.2012

Как говорили другие, вы не можете спать на короткое время (и даже Sleep (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.
person gabr    schedule 14.01.2012
comment
Там, где это возможно (с последней версией Delphi), я рекомендую использовать Diagnostic.TStopWatch для отслеживания прошедшего времени: он разрешает доступ к таймерам с высоким разрешением, если они доступны. - person menjaraz; 14.01.2012

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

person Ed Heal    schedule 14.01.2012
comment
..другой готовый поток может не быть, и в этом случае Yield ничего не делает. - person Martin James; 16.01.2012

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

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

person Arno    schedule 12.07.2012

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

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

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

so

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

person younes zeboudj    schedule 03.02.2016