Partilhar via


User-Mode Agendamento

Advertência

A partir do Windows 11, o agendamento de modo de usuário não é suportado. Todas as chamadas falham com o erro ERROR_NOT_SUPPORTED.

O agendamento de modo de usuário (UMS) é um mecanismo leve que os aplicativos podem usar para agendar seus próprios threads. Uma aplicação pode mudar entre threads UMS em modo de utilizador sem envolver o agendador do sistema e reassumir o controlo 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 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 multiprocessador ou multicore. Para tirar proveito do UMS, um aplicativo deve implementar um componente do agendador que gerencia os threads UMS do aplicativo e determina quando eles devem ser executados. Os desenvolvedores devem considerar se seus requisitos de desempenho de aplicativo justificam o trabalho envolvido no desenvolvimento de tal componente. Aplicativos com requisitos de desempenho moderado podem ser melhor atendidos permitindo que o agendador do sistema programe seus threads.

O UMS está disponível para aplicativos de 64 bits executados nas versões AMD64 e Itanium do Windows 7 e Windows Server 2008 R2 até o Windows 10 Versão 21H2 e Windows Server 2022. Este recurso não está disponível no Arm64, versões de 32 bits do Windows ou no 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 do agendador UMS para cada processador no qual a aplicação executará threads (tarefas) de trabalho UMS.
  • Cria threads de trabalho UMS para executar as tarefas da aplicação.
  • 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, onde o sistema enfileira os threads após terem concluído o processamento no kernel. Isso inclui threads de trabalho recém-criados e threads anteriormente bloqueados numa chamada do sistema que são 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 numa chamada do sistema, ou um thread de trabalho cede explicitamente o controlo.
  • Executa tarefas de limpeza para threads de trabalho que terminaram a execução.
  • Executa um desligamento ordenado do agendador quando solicitado pelo aplicativo.

Thread do Agendador UMS

Uma tarefa do agendador UMS é uma tarefa comum que se converteu em UMS ao chamar 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 com o 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 mais informações, consulte a função do ponto de entrada do agendador UMS mais adiante neste tópico.

Um aplicativo pode criar um thread do 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, que tende a excluir threads não relacionados da execução nesse processador, reservando-o efetivamente para esse thread do agendador. Lembre-se de que definir a afinidade de thread dessa maneira pode afetar o desempenho geral do sistema, prejudicando outros processos que possam estar a ser executados no sistema. Para obter mais informações sobre afinidade de threads, consulte múltiplos processadores.

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

Um thread de trabalho 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 de 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 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 encadeamentos de trabalho UMS são automaticamente enfileirados na lista de conclusão especificada quando os encadeamentos foram criados. Os threads de trabalho bloqueados anteriormente também são enfileirados para a 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 tópicos do agendador UMS, e um tópico do agendador pode recuperar contextos UMS de qualquer lista de conclusão para a qual tenha um ponteiro.

Cada lista de conclusão tem um evento associado que o sistema sinaliza quando enfileira uma ou mais tarefas de trabalho numa lista vazia. A função GetUmsCompletionListEvent recupera um identificador para o evento para uma lista de conclusão especificada. Uma aplicação pode esperar por mais de um evento da lista de conclusão, juntamente com outros eventos que fazem sentido para a aplicação.

Função de 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 momentos:

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

O parâmetro Reason da funçãoUmsSchedulerProc especifica o motivo pelo qual a função de ponto de entrada foi chamada. Se a função de ponto de entrada é 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 produziu, o parâmetro SchedulerParam contém dados especificados pelo chamador de UmsThreadYield. Se a função de ponto de entrada foi chamada porque um thread de trabalhador UMS bloqueou no kernel, o parâmetro SchedulerParam é 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 uma linha de execução de trabalho estiver bloqueada, a função de ponto de entrada do agendador poderá executar a próxima linha de execução de trabalho UMS disponível e pronta.

Quando a função de ponto de entrada do agendador é chamada, o agendador do aplicativo deve tentar recuperar todos os itens na sua lista de conclusão associada chamando a função DequeueUmsCompletionListItems. Esta 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 prontas do agendador e só então executar threads UMS da fila de threads prontas.

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

Execução de Threads UMS

Um thread de trabalho UMS recém-criado é enfileirado para a 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 executar, 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 UMS é executado até render chamando o UmsThreadYield função, bloqueia ou termina.

Práticas recomendadas do UMS

Os aplicativos que implementam 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 obter 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 os threads de trabalho UMS. Isso inclui bloqueios criados por aplicativos e bloqueios de sistema que são 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 carregue uma DLL. A thread de execução adquire o bloqueio do carregador e bloqueia. O sistema chama a função de entrada do agendador, que então carrega uma DLL. Isso causa um deadlock, porque 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, delegue o trabalho que pode compartilhar bloqueios com threads de trabalho UMS para um thread de trabalho UMS dedicado ou um thread não-UMS.
  • UMS é mais eficiente quando a maioria do processamento é feito no modo de usuário. Sempre que possível, evite fazer chamadas de sistema em threads de trabalho UMS.
  • Os threads de trabalho UMS não devem assumir que o agendador do sistema está sendo usado. Esta 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 como esperado e pode quebrar quando chamado por um thread UMS.
  • O sistema pode precisar bloquear o contexto de thread de um thread de trabalho UMS. Por exemplo, uma chamada de procedimento assíncrono em modo kernel pode alterar o contexto do thread UMS, portanto, é necessário bloquear o contexto do thread. 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.