O C ++ 14 parece ter omitido um mecanismo para verificar se um std::mutex
está bloqueado ou não. Veja esta pergunta SO:
https://stackoverflow.com/questions/21892934/how-to-assert-if-a-stdmutex-is-locked
Existem várias maneiras de contornar isso, por exemplo, usando;
std::mutex::try_lock()
std::unique_lock::owns_lock()
Mas nenhuma dessas soluções é particularmente satisfatória.
try_lock()
tem permissão para retornar um falso negativo e tem um comportamento indefinido se o encadeamento atual bloqueou o mutex. Também tem efeitos colaterais. owns_lock()
requer a construção de um unique_lock
em cima do original std::mutex
.
Obviamente, eu poderia fazer o meu próprio, mas prefiro entender as motivações para a interface atual.
A capacidade de verificar o status de um mutex (por exemplo std::mutex::is_locked()
) não parece uma solicitação esotérica para mim, então suspeito que o Comitê Padrão tenha omitido deliberadamente esse recurso em vez de ser uma supervisão.
Por quê?
Edit: Ok, então talvez este caso de uso não seja tão comum quanto eu esperava, então ilustrarei meu cenário específico. Eu tenho um algoritmo de aprendizado de máquina que é distribuído em vários threads. Cada encadeamento opera de forma assíncrona e retorna para um pool principal depois de concluir um problema de otimização.
Em seguida, bloqueia um mutex mestre. O encadeamento deve, então, escolher um novo pai para modificar uma prole, mas só pode escolher entre os pais que atualmente não têm filhos que estão sendo otimizados por outros encadeamentos. Portanto, preciso fazer uma pesquisa para encontrar pais que não estão bloqueados no momento por outro segmento. Não há risco de o status do mutex ser alterado durante a pesquisa, pois o mutex do encadeamento principal está bloqueado. Obviamente, existem outras soluções (atualmente estou usando um sinalizador booleano), mas achei que o mutex oferece uma solução lógica para esse problema, pois existe para fins de sincronização entre threads.
is_locked
?Respostas:
Eu posso ver pelo menos dois problemas graves com a operação sugerida.
O primeiro já foi mencionado em um comentário por @ gnasher729 :
A única maneira de garantir que a propriedade "está bloqueada no momento" de um mutex não seja alterada é: bem, bloqueie-a você mesmo.
O segundo problema que vejo é que, a menos que você bloqueie um mutex, seu thread não será sincronizado com o thread que havia bloqueado o mutex anteriormente. Portanto, nem sequer está bem definido falar sobre “antes” e “depois” e se o mutex está bloqueado ou não, é como perguntar se o gato de Schrödiger está vivo no momento sem tentar abrir a caixa.
Se eu entendi corretamente, os dois problemas seriam discutíveis em seu caso particular, graças ao bloqueio do mestre mutex. Mas esse não me parece um caso particularmente comum, por isso acho que o comitê fez a coisa certa ao não adicionar uma função que possa ser útil em cenários muito especiais e causar danos a todos os outros. (No espírito de: “Tornar as interfaces fáceis de usar corretamente e difíceis de usar incorretamente.”)
E se eu puder dizer, acho que a configuração que você possui atualmente não é a mais elegante e pode ser refatorada para evitar o problema completamente. Por exemplo, em vez de o thread mestre verificar todos os pais em potencial por um que não esteja bloqueado no momento, por que não manter uma fila de pais prontos? Se um encadeamento deseja otimizar outro, ele abre o próximo da fila e, assim que tiver novos pais, os adiciona à fila. Dessa forma, você nem precisa do thread principal como coordenador.
fonte
Parece que você está usando os mutexes secundários para não bloquear o acesso a um problema de otimização, mas para determinar se um problema de otimização está sendo otimizado agora ou não.
Isso é completamente desnecessário. Eu teria uma lista de problemas que precisam ser otimizados, uma lista de problemas que estão sendo otimizados agora e uma lista de problemas que foram otimizados. (Não considere "lista" literalmente, considere "qualquer estrutura de dados apropriada).
As operações de adicionar um novo problema à lista de problemas não otimizados ou de mover um problema de uma lista para a seguinte seriam realizadas sob a proteção do único mutex "mestre".
fonte
std::mutex
é apropriado para essa estrutura de dados?std::mutex
depende de uma implementação mutex definida pelo sistema operacional que pode levar recursos (por exemplo, identificadores) limitados e lentos para alocar e / ou operar. O uso de um único mutex para bloquear o acesso a uma estrutura de dados interna provavelmente será muito mais eficiente e possivelmente mais escalável.Como outros disseram, não há nenhum caso de uso
is_locked
em que um mutex tenha algum benefício, é por isso que a função não existe.O caso com o qual você está tendo problemas é incrivelmente comum, é basicamente o que os threads de trabalho fazem, que são um dos, se não implementações de threads a mais comum.
Você tem uma prateleira com 10 caixas. Você tem 4 trabalhadores trabalhando com essas caixas. Como você garante que os quatro trabalhadores trabalhem em caixas diferentes? O primeiro trabalhador tira uma caixa da prateleira antes de começar a trabalhar nela. O segundo trabalhador vê 9 caixas na prateleira.
Não há mutexes para bloquear as caixas; portanto, não é necessário ver o estado do mutex imaginário na caixa e abusar de um mutex como um booleano está errado. O mutex trava a prateleira.
fonte
Além das duas razões apresentadas na resposta de 5gon12eder acima, gostaria de acrescentar que não é necessário nem desejável.
Se você já está segurando um mutex, é melhor saber que está segurando! Você não precisa perguntar. Assim como possuir um bloco de memória ou qualquer outro recurso, você deve saber exatamente se o possui ou não e quando é apropriado liberar / excluir o recurso.
Se não for esse o caso, seu programa foi mal projetado e você está enfrentando problemas.
Se você precisar acessar o recurso compartilhado protegido pelo mutex, e ainda não o tiver, precisará adquiri-lo. Não há outra opção, caso contrário, a lógica do seu programa não está correta.
Você pode achar que o bloqueio é aceitável ou inaceitável, em ambos os casos,
lock()
outry_lock()
dará o comportamento desejado. Tudo o que você precisa saber, de maneira positiva e sem dúvida, é se você adquiriu o mutex com sucesso (o valor de retornotry_lock
indica). É irrelevante se alguém o segura ou se você tem um fracasso falso.Em todos os outros casos, sem rodeios, não é da sua conta. Você não precisa saber e não deve fazer suposições (para os problemas de pontualidade e sincronização mencionados na outra pergunta).
fonte
try_lock
usar o primeiro recurso e, se esse falhar, tentar o segundo. Exemplo: Três conexões persistentes em pool com o servidor de banco de dados e você precisa usar uma para enviar um comando.is_locked()
esse que possam facilitar esse comportamento.is_locked
, existe uma solução muito melhor para o seu problema do que a que você tem em mente.Você pode querer usar atomic_flag com a ordem de memória padrão. Ele não possui corridas de dados e nunca lança exceções, como o mutex faz com várias chamadas de desbloqueio (e aborta incontrolavelmente, devo acrescentar ...). Como alternativa, existe atômico (por exemplo, atômico [bool] ou atômico [int] (com colchetes triangulares, não [])), que possui boas funções como load e compare_exchange_strong.
fonte
Quero adicionar um caso de uso para isso: isso permitiria que uma função interna assegurasse como pré-condição / asserção que o chamador está de fato segurando a trava.
Para classes com várias funções internas, e possivelmente muitas funções públicas chamando-as, isso poderia garantir que alguém adicionando outra função pública chamando a interna realmente adquirisse o bloqueio.
fonte