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?
fonte
Respostas:
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.
fonte
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?
fonte
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".
fonte