Por que a maioria das implementações mutex é injusta?

11

Entendo que as implementações mais populares de um mutex (por exemplo, std :: mutex em C ++) não garantem imparcialidade - ou seja, elas não garantem que, em casos de contenção, o bloqueio seja adquirido por threads na ordem em que eles chamado lock (). De fato, é até possível (embora seja esperançosamente incomum) que, em casos de alta contenção, alguns dos threads que aguardam a aquisição do mutex possam nunca adquiri-lo.

Isso parece um comportamento inútil para mim - parece-me que um mutex justo produziria um comportamento mais alinhado com o que um programador iria querer / esperar.

A razão apontada para o motivo pelo qual os mutexes normalmente não são implementados para ser justo é o "desempenho", mas eu gostaria de entender melhor o que isso significa - em particular, como o relaxamento do requisito de justiça do mutex melhora o desempenho? Parece que um mutex "justo" seria trivial de implementar - basta fazer com que lock () acrescente o encadeamento de chamada ao final da lista vinculada do mutex antes de colocar o encadeamento em suspensão e, em seguida, faça com unlock () que apareça o próximo encadeamento de o chefe da mesma lista e acorde.

Que insight de implementação de mutex estou perdendo aqui, que explicaria por que foi considerado útil sacrificar a justiça por um melhor desempenho?

Jeremy Friesner
fonte
11
Essa lista vinculada para cada mutex teria que ser uma estrutura de dados compartilhada, certo? Então, como você vai impedir a corrida de dados sem diminuir o desempenho?
user3543290
Usando um mecanismo de lista vinculada sem bloqueio, eu acho. Qual estrutura de dados um mutex injusto usa para encontrar o próximo thread a ser ativado?
Jeremy Friesner
11
Você terá que procurar isso, mas uma lista vinculada sem bloqueio garante justiça? Acho que você descobrirá que é difícil obter garantias como imparcialidade na programação simultânea.
user3543290

Respostas:

7

A resposta de Jim Sawyer aponta para uma resposta: Quando você tem tópicos com prioridades diferentes, o comportamento "justo" estará incorreto. Quando você tem vários threads que podem ser executados, o thread de maior prioridade geralmente é aquele que deve ser executado.

No entanto, há um segredo pouco discutido da implementação do sistema operacional, o qual você deve estar ciente, que é o fato de que ocasionalmente os sistemas operacionais executam o código como usuário ao seqüestrar um encadeamento do usuário. Por razões de segurança, a maioria dos sistemas operacionais que fazem isso apenas o fazem enquanto um thread está bloqueado. Quando o sistema operacional é concluído, o encadeamento é suspenso novamente, e isso geralmente tem o efeito de mover o encadeamento para a parte traseira da fila de espera.

Um exemplo típico é um manipulador de sinal no Unix, uma interceptação de sistema assíncrona no VMS ou uma chamada de procedimento assíncrona no Windows NT. São basicamente a mesma coisa: O sistema operacional precisa notificar o processo do usuário de que algum evento aconteceu, e isso é tratado pela execução de código no espaço do usuário.

Muitos serviços do sistema operacional, como E / S assíncrona, são frequentemente implementados sobre esse recurso.

Outro exemplo é se o processo estiver sob o controle de um depurador. Nesse caso, o sistema de depuração pode executar o código como tarefa do usuário por vários motivos.

Pseudônimo
fonte
4

A "inversão de prioridade" é uma das razões pelas quais a justiça pode ser indesejável. Um processo de baixa prioridade atinge o mutex bloqueado e dorme. Então, um processo de prioridade mais alta o atinge e também dorme. Quando o mutex é desbloqueado, qual processo deve ser o próximo?

Jim Sawyer
fonte
Bem-vindo ao site e obrigado por responder a uma pergunta que está parada há um tempo sem respostas! Sua resposta, é claro, está correta, mas acho que poderia ter um pouco mais de detalhes.
David Richerby
3

Um mutex justo passará mais de sua vida bloqueado do que um mutex injusto, sendo o resto igual. Porque um segmento que libera um mutex injusto sempre pode apenas desbloqueá-lo. Mas um segmento que libera um mutex justo só pode desbloqueá-lo quando a fila do garçom estiver vazia. Caso contrário, o encadeamento de liberação deve deixar o mutex bloqueado para o próximo encadeamento, também conhecido como o primeiro encadeamento na fila do garçom, que é retirado da fila e despertado. O mutex permanece bloqueado pelo menos até que o encadeamento acordado recentemente seja agendado em uma CPU, o que pode levar muito tempo se houver muitos encadeamentos executáveis ​​no momento.

E se o encadeamento de liberação tentar recuperar novamente o mesmo mutex, ele deverá se colocar na parte de trás da fila do garçom e dormir. Isso não teria acontecido se o thread não liberasse o mutex para começar. Portanto, isso incentiva mais seções críticas "mais gananciosas".

Jason Ganetsky
fonte