Как заставить поток спать менее чем за миллисекунду в Windows - PullRequest
51 голосов
/ 17 сентября 2008

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

В 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 ]

88 голосов
/ 17 сентября 2008

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

47 голосов
/ 17 сентября 2008

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

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

28 голосов
/ 17 сентября 2008

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

10 голосов
/ 17 сентября 2008

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

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

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

9 голосов
/ 14 июля 2015
#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); в некоторых из моих порывов

6 голосов
/ 17 сентября 2008

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

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

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

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

В любом случае, если вы ищете некоторую детализацию (но помните проблему с пространством пользователя), обращайтесь к Функция QueryPerformanceCounter и функция QueryPerformanceFrequency в MSDN.

5 голосов
/ 17 сентября 2008

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

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

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

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

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

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

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

Код, описание и тестирование можно найти в Windows Timestamp Project

4 голосов
/ 17 сентября 2008

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

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

2 голосов
/ 19 сентября 2009

На самом деле использование этой функции 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;
}
...