Agendamento de modo de usuário

Aviso

A partir de Windows 11, não há suporte para o agendamento no modo de usuário. Todas as chamadas falham com o erro ERROR_NOT_SUPPORTED.

O agendamento do modo de usuário (UMS) é um mecanismo leve que os aplicativos podem usar para agendar seus próprios threads. Um aplicativo pode alternar entre threads UMS no modo de usuário sem envolver o agendador do sistema e recuperar o controle do processador se um thread UMS bloquear no kernel. Os threads ums diferem das fibras porque cada thread umS tem seu próprio contexto de thread em vez de compartilhar o contexto de thread de um único thread. A capacidade de alternar entre threads no modo de usuário torna o UMS mais eficiente do que os pools de threads para gerenciar um grande número de itens de trabalho de curta duração que exigem poucas chamadas do sistema.

O UMS é recomendado para aplicativos com requisitos de alto desempenho que precisam executar com eficiência muitos threads simultaneamente em sistemas multiprocessadores ou multicore. Para aproveitar a UMS, um aplicativo deve implementar um componente de agendador que gerencie os threads UMS do aplicativo e determine quando eles devem ser executados. Os desenvolvedores devem considerar se os requisitos de desempenho do aplicativo justificam o trabalho envolvido no desenvolvimento desse componente. Aplicativos com requisitos de desempenho moderados podem ser melhor atendidos, permitindo que o agendador do sistema agende seus threads.

O UMS está disponível para aplicativos de 64 bits em execução nas versões AMD64 e Itanium do Windows 7 e Windows Server 2008 R2 até Windows 10 versão 21H2 e Windows Server 2022. Esse recurso não está disponível em versões arm64, de 32 bits do Windows ou em Windows 11.

Para obter detalhes, consulte as seguintes seções:

Agendador UMS

O agendador UMS de um aplicativo é responsável por criar, gerenciar e excluir threads UMS e determinar qual thread UMS executar. O agendador de um aplicativo executa as seguintes tarefas:

  • Cria um thread de agendador UMS para cada processador no qual o aplicativo executará threads de trabalho ums.
  • Cria threads de trabalho ums para executar o trabalho do aplicativo.
  • Mantém sua própria fila de threads de trabalho prontos para execução e seleciona threads a serem executados com base nas políticas de agendamento do aplicativo.
  • Cria e monitora uma ou mais listas de conclusão em que o sistema enfileira threads depois que eles terminam o processamento no kernel. Eles incluem threads de trabalho e threads recém-criados bloqueados anteriormente em uma chamada do sistema que se tornam desbloqueados.
  • Fornece uma função de ponto de entrada do agendador para lidar com notificações do sistema. O sistema chama a função de ponto de entrada quando um thread do agendador é criado, um thread de trabalho bloqueia em uma chamada do sistema ou um thread de trabalho gera explicitamente o controle.
  • Executa tarefas de limpeza para threads de trabalho que terminaram de ser executados.
  • Executa um desligamento ordenado do agendador quando solicitado pelo aplicativo.

Thread do Agendador UMS

Um thread do agendador UMS é um thread comum que se converteu em UMS chamando a função EnterUmsSchedulingMode . O agendador do sistema determina quando o thread do agendador UMS é executado com base em sua prioridade em relação a outros threads prontos. O processador no qual o thread do agendador é executado é influenciado pela afinidade do thread, o mesmo que para threads não UMS.

O chamador de EnterUmsSchedulingMode especifica uma lista de conclusão e uma função de ponto de entrada UmsSchedulerProc a ser associada ao thread do agendador UMS. O sistema chama a função de ponto de entrada especificada quando termina de converter o thread de chamada em UMS. A função de ponto de entrada do agendador é responsável por determinar a próxima ação apropriada para o thread especificado. Para obter mais informações, consulte Função de ponto de entrada do agendador ums mais adiante neste tópico.

Um aplicativo pode criar um thread de agendador UMS para cada processador que será usado para executar threads UMS. O aplicativo também pode definir a afinidade de cada thread do agendador UMS para um processador lógico específico, o que tende a excluir threads não relacionados de execução nesse processador, reservando-o efetivamente para esse thread do agendador. Lembre-se de que definir a afinidade de thread dessa forma pode afetar o desempenho geral do sistema, passando fome de outros processos que podem estar em execução no sistema. Para obter mais informações sobre afinidade de thread, consulte Vários processadores.

Threads de trabalho da UMS, contextos de thread e listas de conclusão

Um thread de trabalho da UMS é criado chamando CreateRemoteThreadEx com o atributo PROC_THREAD_ATTRIBUTE_UMS_THREAD e especificando um contexto de thread umS e uma lista de conclusão.

Um contexto de thread UMS representa o estado do thread UMS de um thread de trabalho e é usado para identificar o thread de trabalho em chamadas de função UMS. Ele é criado chamando CreateUmsThreadContext.

Uma lista de conclusão é criada chamando a função CreateUmsCompletionList . Uma lista de conclusão recebe threads de trabalho da UMS que concluíram a execução no kernel e estão prontos para serem executados no modo de usuário. Somente o sistema pode enfileirar threads de trabalho para uma lista de conclusão. Novos threads de trabalho da UMS são automaticamente enfileirados na lista de conclusão especificada quando os threads foram criados. Os threads de trabalho bloqueados anteriormente também são enfileirados na lista de conclusão quando não estão mais bloqueados.

Cada thread do agendador UMS está associado a uma única lista de conclusão. No entanto, a mesma lista de conclusão pode ser associada a qualquer número de threads do agendador UMS e um thread do agendador pode recuperar contextos ums de qualquer lista de conclusão para a qual ele tem um ponteiro.

Cada lista de conclusão tem um evento associado que é sinalizado pelo sistema quando ele enfileira um ou mais threads de trabalho para uma lista vazia. A função GetUmsCompletionListEvent recupera um identificador para o evento para uma lista de conclusão especificada. Um aplicativo pode aguardar mais de um evento de lista de conclusão, juntamente com outros eventos que fazem sentido para o aplicativo.

Função ponto de entrada do agendador UMS

A função de ponto de entrada do agendador de um aplicativo é implementada como uma função UmsSchedulerProc . O sistema chama a função de ponto de entrada do agendador do aplicativo nos seguintes horários:

  • Quando um thread não UMS é convertido em um thread do agendador UMS chamando EnterUmsSchedulingMode.
  • Quando um thread de trabalho umS chama UmsThreadYield.
  • Quando um thread de trabalho um UMS bloqueia em um serviço do sistema, como uma chamada do sistema ou uma falha de página.

O parâmetro Reason da função UmsSchedulerProc especifica o motivo pelo qual a função de ponto de entrada foi chamada. Se a função de ponto de entrada foi chamada porque um novo thread do agendador UMS foi criado, o parâmetro SchedulerParam contém dados especificados pelo chamador de EnterUmsSchedulingMode. Se a função de ponto de entrada foi chamada porque um thread de trabalho umS rendeu, o parâmetro SchedulerParam contém dados especificados pelo chamador de UmsThreadYield. Se a função de ponto de entrada tiver sido chamada porque um thread de trabalho umS foi bloqueado no kernel, o parâmetro SchedulerParam será NULL.

A função de ponto de entrada do agendador é responsável por determinar a próxima ação apropriada para o thread especificado. Por exemplo, se um thread de trabalho estiver bloqueado, a função de ponto de entrada do agendador poderá executar o próximo thread de trabalho ums pronto disponível.

Quando a função de ponto de entrada do agendador é chamada, o agendador do aplicativo deve tentar recuperar todos os itens em sua lista de conclusão associada chamando a função DequeueUmsCompletionListItems . Essa função recupera uma lista de contextos de thread ums que terminaram o processamento no kernel e estão prontos para serem executados no modo de usuário. O agendador do aplicativo não deve executar threads UMS diretamente dessa lista porque isso pode causar um comportamento imprevisível no aplicativo. Em vez disso, o agendador deve recuperar todos os contextos de thread ums chamando a função GetNextUmsListItem uma vez para cada contexto, inserir os contextos de thread UMS na fila de threads pronta do agendador e, em seguida, executar threads UMS na fila de threads pronta.

Se o agendador não precisar aguardar vários eventos, ele deverá chamar DequeueUmsCompletionListItems com um parâmetro de tempo limite diferente de zero para que a função aguarde no evento de lista de conclusão antes de retornar. Se o agendador precisar aguardar vários eventos de lista de conclusão, ele deverá chamar DequeueUmsCompletionListItems com um parâmetro de tempo limite zero para que a função retorne imediatamente, mesmo que a lista de conclusão esteja vazia. Nesse caso, o agendador pode aguardar explicitamente os eventos da lista de conclusão, por exemplo, usando WaitForMultipleObjects.

Execução do thread UMS

Um thread de trabalho umS recém-criado é enfileirado na lista de conclusão especificada e não começa a ser executado até que o agendador UMS do aplicativo o selecione para ser executado. Isso difere dos threads não UMS, que o agendador do sistema agenda automaticamente para execução, a menos que o chamador crie explicitamente o thread suspenso.

O agendador executa um thread de trabalho chamando ExecuteUmsThread com o contexto UMS do thread de trabalho. Um thread de trabalho da UMS é executado até que ele produz chamando a função UmsThreadYield , blocos ou termina.

Práticas recomendadas do UMS

Os aplicativos que implementam a UMS devem seguir estas práticas recomendadas:

  • As estruturas subjacentes para contextos de thread ums são gerenciadas pelo sistema e não devem ser modificadas diretamente. Em vez disso, use QueryUmsThreadInformation e SetUmsThreadInformation para recuperar e definir informações sobre um thread de trabalho umS.
  • Para ajudar a evitar deadlocks, o thread do agendador UMS não deve compartilhar bloqueios com threads de trabalho ums. Isso inclui bloqueios criados pelo aplicativo e bloqueios do sistema adquiridos indiretamente por operações como alocar do heap ou carregar DLLs. Por exemplo, suponha que o agendador execute um thread de trabalho umS que carrega uma DLL. O thread de trabalho adquire o bloqueio e os blocos do carregador. O sistema chama a função de ponto de entrada do agendador, que carrega uma DLL. Isso causa um deadlock, pois o bloqueio do carregador já está mantido e não pode ser liberado até que o primeiro thread seja desbloqueado. Para ajudar a evitar esse problema, dedique o trabalho que pode compartilhar bloqueios com threads de trabalho ums para um thread de trabalho ums dedicado ou um thread não UMS.
  • O UMS é mais eficiente quando a maioria dos processamentos é feita no modo de usuário. Sempre que possível, evite fazer chamadas do sistema em threads de trabalho da UMS.
  • Os threads de trabalho da UMS não devem assumir que o agendador do sistema está sendo usado. Essa suposição pode ter efeitos sutis; por exemplo, se um thread no código desconhecido definir uma prioridade ou afinidade de thread, o agendador UMS ainda poderá substituí-lo. O código que pressupõe que o agendador do sistema está sendo usado pode não se comportar conforme o esperado e pode ser interrompido quando chamado por um thread UMS.
  • O sistema pode precisar bloquear o contexto de thread de um thread de trabalho umS. Por exemplo, uma APC (chamada de procedimento assíncrono) no modo kernel pode alterar o contexto do thread UMS, portanto, o contexto do thread deve ser bloqueado. Se o agendador tentar executar o contexto de thread UMS enquanto ele estiver bloqueado, a chamada falhará. Esse comportamento é por design e o agendador deve ser projetado para tentar novamente o acesso ao contexto de thread UMS.