Como fazer com que o encadeamento durma menos que um milissegundo no Windows

No Windows, tenho um problema que nunca encontrei no Unix. Isso é como obter um thread para dormir por menos de um milissegundo. No Unix, você normalmente tem várias opções (sleep, sleep e nanosleep) para atender às suas necessidades. No Windows, no entanto, há apenas Suspensão com granularidade em milissegundos.

No Unix, posso usar o select system call para criar um microssegundo que é bem direto:

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

Como posso conseguir o mesmo no Windows?

Isso indica um mal-entendido das funções do sono. O parâmetro que você passa é um tempo mínimo para dormir. Não há garantia de que o encadeamento será ativado após exatamente o horário especificado. Na verdade, os threads não “acordam”, mas são escolhidos para execução pelo agendador. O agendador pode optar por esperar muito mais do que a duração do sono solicitada para ativar um encadeamento, especialmente se outro encadeamento ainda estiver ativo naquele momento.

Como Joel diz, você não pode significativamente “dormir” (isto é, abrir mão da sua CPU programada) por períodos tão curtos. Se você quiser atrasar por um curto período de tempo, então você precisa girar, verificando repetidamente um timer de alta resolução (por exemplo, o ‘timer de desempenho’) e esperando que algo de alta prioridade não o antecipe de qualquer maneira.

Se você realmente se importa com atrasos precisos de tempos tão curtos, você não deveria estar usando o Windows.

Use os timers de alta resolução disponíveis em winmm.lib. Veja isto para um exemplo.

Sim, você precisa entender os quantums do seu sistema operacional. No Windows, você nem estará recebendo tempos de resolução de 1 ms, a menos que você altere o quantum de tempo para 1 ms. (Usando, por exemplo, timeBeginPeriod () / timeEndPeriod ()) Isso ainda não garante realmente nada. Mesmo um pouco de carga ou um único driver de dispositivo de baixa qualidade vai jogar tudo fora.

SetThreadPriority () ajuda, mas é bastante perigoso. Drivers de dispositivos ruins ainda podem arruinar você.

Você precisa de um ambiente de computação ultracontrolado para fazer esse material feio funcionar.

 #include  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); } 

sim ele usa algumas funções do kernel não documentadas, mas funciona muito bem, eu uso o SleepShort (0.5); em alguns dos meus threds

Se você quer muita granularidade, está no lugar errado (no espaço do usuário).

Lembre-se de que, se você estiver no espaço do usuário, seu tempo nem sempre será preciso.

O agendador pode iniciar seu encadeamento (ou aplicativo) e agendá-lo, portanto, você depende do agendador do sistema operacional.

Se você está procurando algo preciso, você precisa ir: 1) No espaço do kernel (como drivers) 2) Escolha um RTOS.

De qualquer forma, se você estiver procurando por alguma granularidade (mas lembre-se do problema com o espaço do usuário), procure a function QueryPerformanceCounter e a function QueryPerformanceFrequency no MSDN.

Como várias pessoas apontaram, o sono e outras funções relacionadas são, por padrão, dependentes do “tick do sistema”. Esta é a unidade mínima de tempo entre as tarefas do SO; o agendador, por exemplo, não será executado mais rápido do que isso. Mesmo com um SO em tempo real, o tick do sistema geralmente não é menor do que 1 ms. Embora seja sintonizável, isso tem implicações para todo o sistema, não apenas para a sua funcionalidade de suspensão, porque seu agendador estará sendo executado com mais freqüência e, potencialmente, aumentando a sobrecarga do seu sistema operacional (tempo gasto para o agendador tempo que uma tarefa pode ser executada).

A solução para isso é usar um dispositivo de relógio externo de alta velocidade. A maioria dos sistemas Unix permite que você especifique para os seus timeres e para um relógio diferente, ao contrário do relógio do sistema padrão.

O que você está esperando para isso requer tal precisão? Em geral, se você precisar especificar esse nível de precisão (por exemplo, devido a uma dependência de algum hardware externo), você está na plataforma errada e deve examinar um sistema operacional em tempo real.

Caso contrário, você deve considerar se há um evento no qual possa sincronizar ou, no pior caso, apenas aguardar a CPU e usar a API de contador de alto desempenho para medir o tempo decorrido.

Geralmente um sono durará pelo menos até a próxima interrupção do sistema ocorrer. No entanto, isso depende das configurações dos resources do timer de multimídia. Pode ser configurado para algo próximo a 1 ms, alguns hardwares até permitem executar em períodos de interrupção de 0,9765625 (o ActualResolution fornecido por NtQueryTimerResolution mostrará 0,9766, mas isso é realmente errado. Eles simplesmente não podem colocar o número correto no formato ActualResolution . 0,9765625ms a 1024 interrupções por segundo).

Há uma exceção que nos permite escaping do fato de que pode ser impossível dormir por menos do que o período de interrupção: é o famoso Sleep(0) . Esta é uma ferramenta muito poderosa e não é usada com a frequência que deveria! Ele libera o lembrete do intervalo de tempo do segmento. Dessa forma, o encadeamento será interrompido até que o planejador force o encadeamento a obter novamente o serviço cpu. Sleep(0) é um serviço asynchronous, a chamada forçará o agendador a reagir independentemente de uma interrupção.

Uma segunda maneira é o uso de um waitable object . Uma function de espera como WaitForSingleObject() pode esperar por um evento. Para ter um encadeamento em espera por qualquer hora, também vezes no regime de microssegundos, o encadeamento precisa configurar algum encadeamento de serviço que gerará um evento no atraso desejado. O thread “adormecido” configurará este encadeamento e, em seguida, fará uma pausa na function de espera até que o encadeamento de serviço defina o evento sinalizado.

Desta forma, qualquer segmento pode “dormir” ou esperar por qualquer momento. O encadeamento de serviços pode ser de grande complexidade e pode oferecer serviços de todo o sistema, como events programados na resolução de microssegundos. No entanto, resolução de microssegundos pode forçar o segmento de serviço a girar em um serviço de tempo de alta resolução por no máximo um período de interrupção (~ 1ms). Se forem tomados cuidados, isso pode funcionar muito bem, especialmente em sistemas com múltiplos processadores ou múltiplos núcleos. Uma rotação de uma ms não causa danos consideráveis ​​no sistema de vários núcleos, quando a máscara de afinidade para o encadeamento de chamada e o encadeamento de serviço é cuidadosamente manipulada.

Código, descrição e teste podem ser visitados no projeto de carimbo de data / hora do Windows

Eu tenho o mesmo problema e nada parece ser mais rápido que um ms, mesmo o Sleep (0). Meu problema é a comunicação entre um cliente e um aplicativo de servidor onde eu uso a function _InterlockedExchange para testar e definir um pouco e, em seguida, eu durmo (0).

Eu realmente preciso executar milhares de operações por segundo dessa maneira e não funciona tão rápido quanto eu planejei.

Como tenho um thin client lidando com o usuário, que por sua vez chama um agente que, em seguida, fala com um thread, moverei em breve para mesclar o thread com o agente, para que nenhuma interface de evento seja necessária.

Só para dar a vocês uma ideia de quão lento é esse Sleep, fiz um teste por 10 segundos executando um loop vazio (conseguindo algo como 18.000.000 de loops) enquanto que com o evento no lugar eu só consegui 180.000 loops. Ou seja, 100 vezes mais devagar!

Na verdade, o uso dessa function de adormecimento causará um grande memory leaks / recurso. (dependendo de quantas vezes chamado)

use esta versão corrigida (desculpe não pode editar?)

 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; } 

Como todos mencionaram, realmente não há garantias sobre o tempo de sono. Mas ninguém quer admitir que às vezes, em um sistema ocioso, o comando usleep pode ser muito preciso. Especialmente com um kernel sem tick. O Windows Vista tem isso e o Linux tem desde o 2.6.16.

Kernels Tickless existe para ajudar a melhorar a vida útil dos laptops: o utilitário powertop da Intel.

Nessa condição, calculei que o comando usleep do Linux respeitou muito de perto o tempo de sono solicitado, chegando a meia dúzia de microssegundos.

Então, talvez o OP queira algo que trabalhe mais ou menos na maioria das vezes em um sistema inativo, e seja capaz de pedir um agendamento de micro segundos! Eu realmente quero isso no Windows também.

Também Sleep (0) soa como boost :: thread :: yield (), cuja terminologia é mais clara.

Eu me pergunto se os bloqueios de Boost- Timed têm uma precisão melhor. Porque então você poderia apenas bloquear um mutex que ninguém nunca libera, e quando o tempo limite for atingido, continue em … Timeouts são definidos com boost :: system_time + boost :: milliseconds & cie (xtime está obsoleto).

Tente boost :: xtime e um timed_wait ()

tem precisão de nanossegundos.

Apenas use o Sleep (0). 0 é claramente menor que um milissegundo. Agora, isso soa engraçado, mas estou falando sério. Sleep (0) diz ao Windows que você não tem nada a fazer agora, mas que você quer ser reconsiderado assim que o agendador for executado novamente. E como obviamente o thread não pode ser programado para ser executado antes que o próprio programador seja executado, esse é o menor atraso possível.

Note que você pode passar um número de microssegundos para o seu uso, mas o mesmo faz com o uso do void (__ int64 t) {Sleep (t / 1000); } – não há garantias para realmente dormir esse período.

Tente usar SetWaitableTimer …

Função de sono que é muito menos do que um milésimo de segundo, talvez

Eu achei que o sono (0) funcionou para mim. Em um sistema com quase 0% de carga no cpu no gerenciador de tarefas, eu escrevi um programa de console simples e a function sleep (0) dormia por um período consistente de 1 a 3 microssegundos, o que é muito menos do que um milissegundo.

Mas a partir das respostas acima neste segmento, eu sei que a quantidade de sono (0) pode variar muito mais do que isso em sistemas com uma grande carga de CPU.

Mas pelo que entendi, a function sleep não deve ser usada como timer. Ele deve ser usado para fazer o programa usar a menor porcentagem possível da CPU e executar com a maior freqüência possível. Para os meus propósitos, como mover um projétil através da canvas em um videogame muito mais rápido que um pixel por milissegundo, o sleep (0) funciona, eu acho.

Você apenas se certificaria de que o intervalo de sono fosse muito menor do que a maior quantidade de tempo que ele iria dormir. Você não usa o sono como um timer, mas apenas para fazer o jogo usar a quantidade mínima de porcentagem de CPU possível. Você usaria uma function separada que não tem nada a ver com o sono para saber quando uma determinada quantidade de tempo passou e depois mover o projétil com um pixel pela canvas – por vez, digamos 1/10 de um milissegundo ou 100 microssegundos .

O pseudo-código seria algo assim.

 while (timer1 < 100 microseconds) { sleep(0); } if (timer2 >=100 microseconds) { move projectile one pixel } //Rest of code in iteration here 

Eu sei que a resposta pode não funcionar para problemas ou programas avançados, mas pode funcionar para alguns ou muitos programas.

Se o seu objective é “esperar por um período muito curto”, porque você está fazendo um spinwait , então há níveis crescentes de espera que você pode realizar.

 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; } } 

Então, se o seu objective é esperar apenas um pouquinho , você pode usar algo como:

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

A virtude aqui é que esperamos mais tempo a cada vez, acabando por adormecer completamente.

No Windows, o uso de select força você a include a biblioteca Winsock , que deve ser inicializada assim em seu aplicativo:

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

E então o select não permitirá que você seja chamado sem nenhum socket, então você tem que fazer um pouco mais para criar um método 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); } 

Todos esses methods criados usleep retornam zero quando bem-sucedidos e não-zero para erros.