Как заставить поток спать менее миллисекунды в Windows

StackOverflow https://stackoverflow.com/questions/85122

  •  01-07-2019
  •  | 
  •  

Вопрос

В Windows у меня возникла проблема, с которой я никогда не сталкивался в Unix.Вот как заставить поток спать менее чем на одну миллисекунду.В Unix у вас обычно есть несколько вариантов (sleep, usleep и nanosleep) в соответствии с вашими потребностями.Однако в Windows есть только Спать с миллисекундной детализацией.

В Unix я могу использовать команду select системный вызов для создания микросекундного сна, который довольно прост:

int usleep(long usec)
{
    struct timeval tv;
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, 0, &tv);
}

Как я могу добиться того же в Windows?

Это было полезно?

Решение 18

В Windows использование select заставляет вас включить Уинсок библиотека, которую необходимо инициализировать в вашем приложении следующим образом:

WORD wVersionRequested = MAKEWORD(1,0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);

И тогда выбор не позволит вам вызываться без какого-либо сокета, поэтому вам придется сделать еще немного, чтобы создать метод микросна:

int usleep(long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, &dummy, &tv);
}

Все эти созданные usleep методы возвращают ноль в случае успеха и ненулевое значение в случае ошибок.

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

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

Как говорит Джоэл, вы не можете осмысленно «спать» (т.освободить запланированный процессор) на такие короткие периоды времени.Если вы хотите сделать задержку на какое-то короткое время, то вам нужно покрутиться, неоднократно проверяя таймер подходящего высокого разрешения (например,«таймер производительности») и надеясь, что что-то с высоким приоритетом все равно не вытеснит вас.

Если вас действительно волнует точная задержка в такие короткие сроки, вам не следует использовать Windows.

Используйте таймеры высокого разрешения, доступные в winmm.lib.Видеть этот для примера.

Да, вам нужно понимать кванты времени вашей ОС.В Windows вы даже не получите время разрешения 1 мс, если не измените квант времени на 1 мс.(Используя, например, timeBeginPeriod()/timeEndPeriod()). Это все равно ничего не гарантирует.Даже небольшая нагрузка или один дрянной драйвер устройства все скинет.

SetThreadPriority() помогает, но весьма опасен.Плохие драйверы устройств все равно могут вас испортить.

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

#include <Windows.h>

static NTSTATUS(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");

static NTSTATUS(__stdcall *ZwSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution) = (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");




static void SleepShort(float milliseconds) {
    static bool once = true;
    if (once) {
        ULONG actualResolution;
        ZwSetTimerResolution(1, true, &actualResolution);
        once = false;
    }

    LARGE_INTEGER interval;
    interval.QuadPart = -1 * (int)(milliseconds * 10000.0f);
    NtDelayExecution(false, &interval);
}

да, он использует некоторые недокументированные функции ядра, но работает очень хорошо, я использую SleepShort(0.5);в некоторых моих тредах

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

Помните, что если вы находитесь в пользовательском пространстве, ваше время не всегда точное.

Планировщик может запустить ваш поток (или приложение) и запланировать его, так что вы зависите от планировщика ОС.

Если вы ищете что-то точное, вам нужно пойти:1) В пространстве ядра (например, драйверов) 2) Выберите RTOS.

В любом случае, если вы ищете гранулярность (но помните проблему с пространством пользователя), посмотрите на функцию QueryperformanceCounter и функцию QueryPerformanceFrequence в MSDN.

Как отметили несколько человек, сон и другие связанные функции по умолчанию зависят от «системной отметки».Это минимальная единица времени между задачами ОС;планировщик, например, не будет работать быстрее этого.Даже в ОС реального времени системный тик обычно не меньше 1 мс.Хотя его можно настраивать, это влияет на всю систему, а не только на функциональность сна, поскольку ваш планировщик будет запускаться чаще и потенциально увеличит накладные расходы вашей ОС (время работы планировщика по сравнению с продолжительностью работы планировщика).время, в течение которого может выполняться задача).

Решением этой проблемы является использование внешнего высокоскоростного устройства синхронизации.Большинство систем Unix позволят вам указать таймеры и другие часы, которые будут использоваться, в отличие от системных часов по умолчанию.

Чего вы ждете, что требует такой точности?В общем, если вы нуждаться чтобы указать этот уровень точности (например,из-за зависимости от какого-то внешнего оборудования) вы находитесь на неправильной платформе и вам следует посмотреть на ОС реального времени.

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

Обычно спящий режим длится как минимум до тех пор, пока не произойдет следующее системное прерывание.Тем не менее, это зависит от настройки ресурсов мультимедийного таймера.Это может быть установлено на что -то близкое к 1 мс, некоторые оборудование даже позволяет работать в периоды прерывания 0,9765625 (Фактическое разрешение предоставлено NtQueryTimerResolution покажет 0,9766, но на самом деле это неверно.Они просто не могут ввести правильный номер в Фактическое разрешение формат.Это 0,9765625 мс при 1024 прерываниях в секунду).

Есть одно исключение, которое позволяет нам избежать того факта, что спать меньше периода прерывания может оказаться невозможным:Это знаменитый Sleep(0).Это очень мощный инструмент, и он не используется так часто, как следовало бы!Он отказывается от напоминания о временном интервале потока.Таким образом, поток остановится до тех пор, пока планировщик не заставит поток снова получить обслуживание процессора. Sleep(0) является асинхронной службой, вызов заставит планировщик реагировать независимо от прерывания.

Второй способ – использование waitable object.Функция ожидания, например WaitForSingleObject() можно дождаться какого-нибудь события.Чтобы поток находился в спящем режиме в любое время, в том числе в микросекундном режиме, потоку необходимо настроить некоторый сервисный поток, который будет генерировать событие с желаемой задержкой.«Спящий» поток настроит этот поток, а затем приостановит функцию ожидания до тех пор, пока поток обслуживания не установит сигнальное событие.

Таким образом, любой поток может «спать» или ждать в любое время.Поток обслуживания может быть очень сложным и может предлагать общесистемные услуги, такие как синхронизированные события с микросекундным разрешением.Однако микросекундное разрешение может заставить поток службы вращаться в службе времени с высоким разрешением не более одного периода прерывания (~ 1 мс).Если заботиться о том, что это может работать очень хорошо, особенно на многопроцессорных или многоядерных системах.Вращение на одну миллисекунду не причинит значительного вреда в многоядерной системе, если маска сходства для вызывающего потока и потока обслуживания тщательно обрабатывается.

Код, описание и тестирование можно посетить на странице Проект отметки времени Windows

У меня та же проблема, и кажется, что ничто не работает быстрее, чем мс, даже Sleep(0).Моя проблема заключается в связи между клиентом и серверным приложением, где я использую функцию _InterlockedExchange для тестирования и установки бита, а затем я сплю (0).

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

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

Просто чтобы дать вам представление, насколько медленным является этот режим сна, я провел тест в течение 10 секунд, выполняя пустой цикл (получив примерно 18 000 000 циклов), тогда как при наличии события я получил только 180 000 циклов.То есть в 100 раз медленнее!

На самом деле использование этой функции usleep приведет к большой утечке памяти/ресурсов.(в зависимости от того, как часто звонят)

используйте эту исправленную версию (извините, нельзя редактировать?)

bool usleep(unsigned long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec / 1000000ul;
    tv.tv_usec = usec % 1000000ul;
    bool success = (0 == select(0, 0, 0, &dummy, &tv));
    closesocket(s);
    return success;
}

Как все уже упоминали, на самом деле нет никаких гарантий относительно времени сна.Но никто не хочет признавать, что иногда в простаивающей системе команда usleep может быть очень точной.Особенно с безтиковым ядром.Он есть в Windows Vista, а в Linux он есть с версии 2.6.16.

Существуют ядра Tickless, которые помогают продлить срок службы батареи ноутбуков:ср.Утилита Powertop от Intel.

В этом состоянии мне удалось измерить команду usleep в Linux, которая очень точно соблюдала запрошенное время сна, вплоть до полдюжины микросекунд.

Итак, возможно, ОП хочет что-то, что будет примерно работать большую часть времени в системе на холостом ходу и иметь возможность запрашивать микросекундное планирование!Я бы хотел, чтобы это было и в Windows.

Также Sleep(0) звучит как boost::thread::yield(), и эта терминология более понятна.

Интересно, если Способствовать росту-таймовые замки имеют лучшую точность.Потому что тогда вы могли бы просто заблокировать мьютекс, который никто никогда не освобождает, и когда истечет время ожидания, продолжить…Таймауты устанавливаются с помощью boost::system_time + boost::milliсекунды и cie (xtime устарел).

Попробуйте использовать SetWaitableTimer...

Попробуйте boost::xtime и timed_wait()

имеет наносекундную точность.

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

Обратите внимание, что вы можете передать в функцию usleep число в микросекундах, но то же самое можно сделать и с void usleep(__int64 t) { Sleep(t/1000);} — нет никаких гарантий, что этот период действительно будет спать.

Функция сна, которая намного меньше миллисекунды - может быть

Я обнаружил, что сон(0) мне помог.В системе с загрузкой процессора около 0% в диспетчере задач я написал простую консольную программу, и функция сна (0) спала стабильно 1-3 микросекунды, что намного меньше миллисекунды.

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

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

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

Псевдокод будет выглядеть примерно так.

while (timer1 < 100 microseconds) {
sleep(0);
}

if (timer2 >=100 microseconds) {
move projectile one pixel
}

//Rest of code in iteration here

Я знаю, что ответ может не подойти для сложных задач или программ, но может подойти для некоторых или многих программ.

Если ваша цель — «подождите очень короткое время» потому что ты делаешь спинвейт, то вы можете выполнять все возрастающие уровни ожидания.

void SpinOnce(ref Int32 spin)
{
   /*
      SpinOnce is called each time we need to wait. 
      But the action it takes depends on how many times we've been spinning:

      1..12 spins: spin 2..4096 cycles
      12..32: call SwitchToThread (allow another thread ready to go on time core to execute)
      over 32 spins: Sleep(0) (give up the remainder of our timeslice to any other thread ready to run, also allows APC and I/O callbacks)
   */
   spin += 1;

   if (spin > 32)
      Sleep(0); //give up the remainder of our timeslice
   else if (spin > 12)
      SwitchTothread(); //allow another thread on our CPU to have the remainder of our timeslice
   else
   {
      int loops = (1 << spin); //1..12 ==> 2..4096
      while (loops > 0)
         loops -= 1;
   }
}

Итак, если ваша цель на самом деле — подождать только ненадолго, вы можете использовать что-то вроде:

int spin = 0;
while (!TryAcquireLock()) 
{ 
   SpinOne(ref spin);
}

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

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