it-swarm-pt.tech

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 Sleep com granularidade de milissegundos. 

No Unix, posso usar o uso da chamada de sistema select para criar um microssegundo de sono bastante simples:

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?

50
Jorge Ferreira

Isso indica um mal-entendido das funções do sono. O parâmetro que você passa é um mínimo tempo para dormir. Não há garantia de que o segmento 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 thread, especialmente se outro segmento ainda estiver ativo naquele momento.

88
Joel Coehoorn

Como Joel diz, você não pode significativamente 'dormir' (ou seja, abrir mão da sua CPU programada) por períodos tão curtos. Se você quiser atrasar por algum tempo, então você precisa girar, verificando repetidamente um temporizador de alta resolução (por exemplo, o 'temporizador 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.

47
Will Dean

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

26
Joe Schneider

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 ultra-controlado para fazer essas coisas feias funcionarem.

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

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

8
Oskar Dahlberg

Se você quer muita granularidade, você 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ê está procurando alguma granularidade (mas lembre-se do problema com o espaço do usuário), olhe para Função QueryPerformanceCounter e função QueryPerformanceFrequency no MSDN.

6
user16523

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 (quantidade de tempo para execução do agendador vs. 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 seus temporizadores e para um relógio diferente, ao contrário do relógio padrão do sistema.

5
mbyrne215

Geralmente um sono durará pelo menos até a próxima interrupção do sistema ocorrer. No entanto, isso Depende das configurações dos recursos 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 (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 com 1024 interrupções por segundo).

Há uma exceção que nos permite escapar do fato de que pode ser impossível dormir por menos do que o período de interrupção: é a famosa Sleep(0). Esta é uma ferramenta muito poderosa E não é usada com a freqüê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 assíncrono, a chamada forçará o agendador a reagir independentemente de uma interrupção.

Uma segunda maneira é o uso de um waitable object. Uma função de espera como WaitForSingleObject() pode esperar por um evento. Para que um encadeamento permaneça ativo a qualquer momento, também em 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 função 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 sistema amplo, como eventos 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 Windows Timestamp Project

4
Arno

O que você está esperando para isso requer essa precisão? Em geral, se você precisar especificar esse nível de precisão (por exemplo, devido a uma dependência em 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 do contador de alto desempenho para medir o tempo decorrido.

4
Rob Walker

Na verdade, o uso dessa função de adormecimento causará um grande vazamento de memória/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;
}
2
Hendrik

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 função _InterlockedExchange para testar e definir um pouco e, em seguida, eu durmo (0).

Eu realmente preciso realizar 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 invoca 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!

2
Celso Bressan

Como todos mencionaram, na verdade não há garantias sobre o tempo de sono. Mas ninguém quer admitir que às vezes, em um sistema ocioso, o comando de adormecimento 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: c.f. Utilitário de energia da Intel.

Naquela condição, calculei que o comando usleep do Linux respeitou muito bem 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 maior parte do tempo em um sistema inativo, e seja capaz de pedir um agendamento de micro segundos! Eu realmente gostaria disso no Windows também.

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

Gostaria de saber se Boost - timed locks tem uma precisão melhor. Porque então você poderia apenas travar em um mutex que ninguém nunca libera, e quando o tempo limite for atingido, continue em ... Os tempos limite são definidos com boost :: system_time + boost :: milliseconds & cie (xtime é preterido ).

1
Lightness1024

Tente usar SetWaitableTimer ...

0
andrewrk

Tente boost :: xtime e um timed_wait ()

tem precisão de nanossegundos.

0
theschmitzer

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.

0
MSalters

Se o seu objetivo é "esperar por um curto período de tempo" porque você está fazendo um spinwait , então há níveis crescentes de espera que você pode executar.

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 objetivo é realmente esperar só por 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. 

0
Ian Boyd

Função sleep que é muito menos que um milissegundo - 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 função sleep (0) dormia por 1-3 microssegundos consistentes, o que é muito menos do que um milissegundo.

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

Mas pelo que entendi, a função sleep não deve ser usada como temporizador. Ele deve ser usado para fazer o programa usar a menor porcentagem possível da CPU e executá-la com a maior freqüência possível. Para os meus propósitos, como mover um projétil através da tela 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 temporizador, mas apenas para fazer o jogo usar a quantidade mínima de porcentagem de CPU possível. Você usaria uma função separada que não teria nada a fazer para adormecer para saber quando uma determinada quantidade de tempo passou e, em seguida, mover o projétil com um pixel pela tela - em um tempo de 1/10 de milissegundos 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. 

0
rauprog