Как заставить поток спать менее чем за миллисекунду в Windows
В 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?
19 ответов
На Windows использование select
вынуждает вас включить библиотеку Winsock, которая должна быть инициализирована следующим образом:
WORD wVersionRequested = MAKEWORD(1,0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);
И тогда select не позволит вам вызываться без какого-либо сокета, поэтому вам придется сделать немного больше, чтобы создать метод microsleep:
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. Смотрите это для примера.
#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); в некоторых из моих порывов
Да, вы должны понимать временные кванты вашей ОС. В Windows вы даже не получите разрешение 1 мс, если вы не измените квант времени на 1 мс. (Используя, например, timeBeginPeriod()/timeEndPeriod()) Это все еще ничего не гарантирует. Даже небольшая загрузка или один дрянной драйвер устройства скинут все.
SetThreadPriority () помогает, но довольно опасно. Плохие драйверы устройств все еще могут вас испортить.
Вам нужна ультраконтролируемая вычислительная среда, чтобы все эти уродливые вещи работали вообще.
Обычно сон будет продолжаться, по крайней мере, до следующего системного прерывания. Однако это зависит от настроек ресурсов мультимедийного таймера. Может быть установлено значение, близкое к 1 мс, некоторые аппаратные средства даже позволяют работать с периодами прерывания 0,9765625 (ActualResolution предоставлен NtQueryTimerResolution
покажет 0,9766, но это на самом деле неправильно. Они просто не могут поместить правильный номер в формат ActualResolution. Это 0,9765625мс при 1024 прерываниях в секунду).
Есть одно исключение, которое позволяет нам избежать того факта, что может быть невозможно спать меньше, чем период прерывания: это знаменитый Sleep(0)
, Это очень мощный инструмент, и он используется не так часто, как следовало бы! Отказывается от напоминания о временном интервале потока. Таким образом, поток остановится, пока планировщик не заставит поток снова получить службу процессора. Sleep(0)
это асинхронный сервис, вызов заставит планировщик реагировать независимо от прерывания.
Вторым способом является использование waitable object
, Функция ожидания, как WaitForSingleObject()
может ждать события. Для того, чтобы поток спал в течение какого-то времени, в том числе в микросекундном режиме, поток должен установить какой-либо служебный поток, который будет генерировать событие с желаемой задержкой. "Спящий" поток настроит этот поток и затем приостановит работу функции ожидания, пока служебный поток не установит событие, о котором было сообщено.
Таким образом, любой поток может "спать" или ждать в любое время. Поток службы может быть большой сложности и может предлагать общесистемные услуги, такие как синхронизированные события с микросекундным разрешением. Тем не менее, микросекундное разрешение может привести к тому, что служебный поток будет вращаться в службе времени с высоким разрешением не более одного периода прерывания (~1 мс). Если позаботиться, это может работать очень хорошо, особенно в многопроцессорных или многоядерных системах. Вращение много миллисекунд не наносит значительного ущерба в многоядерной системе, когда маска привязки для вызывающего потока и потока обслуживания тщательно обрабатывается.
Код, описание и тестирование можно посетить в Windows Timestamp Project
Как отмечали несколько человек, режим сна и другие связанные функции по умолчанию зависят от "системного тика". Это минимальная единица времени между задачами ОС; например, планировщик не будет работать быстрее, чем этот. Даже с ОС реального времени системный тик обычно не менее 1 мс. Несмотря на то, что он настраиваемый, это имеет значение для всей системы, а не только для вашей функции сна, потому что ваш планировщик будет работать чаще и потенциально увеличивает нагрузку на вашу ОС (количество времени для запуска планировщика, по сравнению с количеством время выполнения задачи).
Решением этой проблемы является использование внешнего высокоскоростного устройства синхронизации. Большинство систем Unix позволяют вам указывать своим таймерам и таким другим часам использовать, в отличие от системных часов по умолчанию.
Если вам нужна большая степень детализации, вы находитесь не в том месте (в пространстве пользователя).
Помните, что если вы находитесь в пространстве пользователя, ваше время не всегда точное.
Планировщик может запустить ваш поток (или приложение) и запланировать его, так что вы зависите от планировщика ОС.
Если вы ищете что-то точное, вы должны пойти: 1) В пространстве ядра (например, драйверы) 2) Выберите ОСРВ.
В любом случае, если вы ищете некоторую детализацию (но помните о проблеме с пространством пользователя), обратите внимание на функцию QueryPerformanceCounter и функцию QueryPerformanceFrequency в MSDN.
Чего вы ждете, что требует такой точности? В общем, если вам нужно указать этот уровень точности (например, из-за зависимости от какого-либо внешнего оборудования), вы находитесь на неправильной платформе и должны смотреть на ОС реального времени.
В противном случае вам следует подумать, есть ли событие, с которым вы можете синхронизироваться, или, в худшем случае, просто заняты, ожидая ЦП и используя API высокопроизводительного счетчика для измерения истекшего времени.
На самом деле использование этой функции 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;
}
Если ваша цель состоит в том, чтобы "ждать очень короткое время", потому что вы выполняете спин-вайт, тогда вы можете увеличить уровни ожидания.
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);
}
Добродетель здесь заключается в том, что мы каждый раз дольше ждем, в конце концов полностью ложась спать
У меня та же проблема, и кажется, что ничего не быстрее, чем мс, даже Sleep(0). Моя проблема - связь между клиентом и серверным приложением, где я использую функцию _InterlockedExchange, чтобы проверить и установить бит, а затем я сплю (0).
Мне действительно нужно выполнять тысячи операций в секунду таким образом, и это не работает так быстро, как я планировал.
Поскольку у меня есть тонкий клиент, работающий с пользователем, который, в свою очередь, вызывает агента, который затем обращается к потоку, я скоро перейду к объединению потока с агентом, так что никакой интерфейс событий не потребуется.
Просто чтобы дать вам, ребята, представление о том, насколько медлен этот сон, я провел тест в течение 10 секунд, выполняя пустой цикл (получая примерно 18000 000 циклов), тогда как с событием на месте я получил только 180 000 циклов. То есть в 100 раз медленнее!
Как все упоминали, на самом деле нет никаких гарантий относительно времени сна. Но никто не хочет признать, что иногда в режиме ожидания команда usleep может быть очень точной. Особенно с тикающим ядром. В Windows Vista он есть, а в Linux - с 2.6.16.
Ядро Tickless существует, чтобы помочь улучшить жизнь ноутбуков: ср. Утилита Powertop от Intel.
В этом случае я случайно измерил команду usleep в Linux, которая очень точно соблюдала запрошенное время ожидания, до полдюжины микросекунд.
Так что, возможно, ОП хочет что-то, что будет работать в холостом режиме большую часть времени, и сможет запросить микросекундное планирование! Я бы на самом деле хотел этого и на Windows.
Также Sleep(0) звучит как boost::thread::yield(), терминология которого более понятна.
Интересно, имеют ли улучшенные замки лучшую точность. Потому что тогда вы могли бы просто заблокировать мьютекс, который никто никогда не освобождает, и когда истечет время ожидания, продолжайте... Время ожидания устанавливается с помощью boost::system_time + boost::milliseconds & cie (xtime устарело).
Если на машине установлена Windows 10 версии 1803 или новее, вы можете использовать
Функция сна намного меньше миллисекунды, может быть
Я обнаружил, что сон (0) работал на меня. В системе с почти 0% загрузкой процессора в диспетчере задач я написал простую консольную программу, и функция sleep(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
Я знаю, что ответ может не работать для сложных вопросов или программ, но может работать для некоторых или многих программ.
Просто используйте Sleep(0). 0 явно меньше миллисекунды. Это звучит забавно, но я серьезно. Sleep(0) говорит Windows, что вам сейчас нечего делать, но вы хотите, чтобы вас пересмотрели, как только планировщик снова запустится. И поскольку очевидно, что поток не может быть запланирован для запуска до запуска самого планировщика, это самая короткая возможная задержка.
Обратите внимание, что вы можете передать число микросекунды своему уснувшему, но также и void usleep(__int64 t) { Sleep(t/1000); } - нет никаких гарантий фактически спать в этот период.
Попробуйте boost::xtime и timed_wait()
имеет наносекундную точность.