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

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

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

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


После ответа Bens я обнаружил, что Sleep с разными значениями ниже 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)

Ответы

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

Как говорили другие, вы не можете спать в течение короткого промежутка времени (и даже 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.

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

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

так

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