Pergunta

POSIX permite mutexes para ser recursiva. Isso significa que o mesmo thread pode bloquear o mesmo mutex duas vezes e não vai impasse. Claro que também precisa desbloqueá-lo duas vezes, caso contrário, nenhuma outra thread pode obter o mutex. Nem todos os sistemas de suporte pthreads também suportam mutexes recursiva, mas se eles querem ser POSIX conformidade, eles têm que .

Outras APIs (mais APIs de alto nível) também costumam oferecer semáforos, muitas vezes chamado Locks. Alguns sistemas / idiomas (por exemplo cacau Objectivo-C) oferecer ambos, semáforos recursivos e não recursivos. Algumas línguas também oferecem apenas um ou o outro. Por exemplo. em Java mutexes são sempre recursiva (o mesmo segmento pode duas vezes "Sincronizar" no mesmo objeto). Dependendo do que outras funcionalidades fio eles oferecem, não tendo mutexes recursiva pode ser nenhum problema, como eles podem facilmente ser escrito por si (já implementado mutexes recursiva me com base em operações mutex / condição mais simples).

O que eu realmente não entendo: Quais são não-recursiva mutexes bom para? Por que eu iria querer ter um impasse thread se ele bloqueia o mesmo mutex duas vezes? Mesmo linguagens de alto nível que poderiam evitar que (por exemplo, testar se isso vai impasse e lançar uma exceção se faz) normalmente não fazer isso. Eles vão deixar o impasse fio em seu lugar.

É este apenas para casos, quando eu acidentalmente bloqueá-lo duas vezes e só desbloqueá-lo uma vez e, em caso de um mutex recursivo, seria mais difícil encontrar o problema, então ao invés eu tê-lo impasse imediatamente para ver onde o bloqueio incorrecto parece? Mas eu não podia fazer o mesmo com ter um contador de bloqueio devolvido quando o desbloqueio e numa situação, onde estou certo de que lançou o último bloqueio eo contador não é zero, eu posso lançar uma exceção ou registrar o problema? Ou há alguma outra mais útil caso de uso de semáforos não recursiva, que não vejo? Ou é talvez apenas o desempenho, como um mutex não recursivo pode ser ligeiramente mais rápido do que um recursiva? No entanto, eu testei isso e a diferença não é tão grande.

Foi útil?

Solução

A diferença entre um mutex recursivo e não recursivo tem a ver com a propriedade. No caso de um mutex recursivo, o kernel tem de acompanhar o fio que realmente obteve o mutex a primeira vez em torno de modo que ele pode detectar a diferença entre recursão vs. um segmento diferente que deve bloquear vez. Como outra resposta apontou, há uma questão da sobrecarga adicional desta tanto em termos de memória para armazenar Neste contexto, e também os ciclos necessário para mantê-la.

No entanto , há outras considerações em jogo aqui também.

Porque o mutex recursiva tem um senso de propriedade, o segmento que agarra o mutex deve ser o mesmo segmento que libera o mutex. No caso de semáforos não-recursiva, não há nenhum sentido de propriedade e qualquer tópico geralmente pode liberar o mutex não importa qual thread originalmente tomou o mutex. Em muitos casos, este tipo de "mutex" é realmente mais de uma ação de semáforo, onde você não está necessariamente usando o mutex como um dispositivo de exclusão, mas usá-lo como sincronização ou dispositivo de sinalização entre dois ou mais threads.

Outra propriedade que vem com um senso de propriedade em um mutex é a capacidade de suportar a herança de prioridade. Como o kernel pode acompanhar o fio possuir o mutex e também a identidade de todos o bloqueador (s), em uma prioridade sistema de rosca torna-se possível aumentar a prioridade do segmento que atualmente possui o mutex com a prioridade do mais alto thread de prioridade que está atualmente bloqueando a exclusão mútua. Essa herança evita o problema da inversão de prioridade que pode ocorrer em tais casos. (Note-se que nem todos os sistemas suportam a herança de prioridade em tais semáforos, mas é outra característica que se torna possível através da noção de propriedade).

Se você se refere ao clássico do kernel VxWorks RTOS, eles definem três mecanismos:

  • mutex - suportes recursão, e herança opcionalmente prioridade. Este mecanismo é comumente usado para proteger as seções críticas de dados de uma maneira coerente.
  • binário semáforo - sem recursão, nenhuma herança, exclusão simples, tomador e doador não tem que ser mesmo segmento, a liberação de transmissão disponível. Este mecanismo pode ser usado para proteger as seções críticas, mas também é particularmente útil para a sinalização coerente ou sincronização entre threads.
  • contagem semáforo -. Sem recursão ou herança, atua como um contador de recursos coerente a partir de qualquer contagem inicial desejada, tópicos só bloco onde a contagem líquida contra o recurso é zero

Novamente, isso varia um pouco por plataforma -. Especialmente o que eles chamam estas coisas, mas isso deve ser representativa dos conceitos e vários mecanismos em jogo

Outras dicas

A resposta é não eficiência. mutexes não reentrante levar a melhor código.

Exemplo: Um :: foo () adquire o bloqueio. Em seguida, chama B :: bar (). Isso funcionou bem quando você escreveu. Mas algum tempo depois alguém muda B :: bar () para chamar A :: baz (), que também adquire o bloqueio.

Bem, se você não tem mutexes recursiva, este impasses. Se você tem deles, ele é executado, mas pode quebrar. A :: foo () pode ter deixado o objeto em um estado inconsistente antes de chamar bar (), no pressuposto de que baz () não poderia ter executado porque ele também adquire o mutex. Mas provavelmente não deve correr! A pessoa que escreveu A :: foo () assumiu que ninguém poderia chamar A :: baz (), ao mesmo tempo -. Isso é toda a razão que ambos os métodos adquiriu o bloqueio

O modelo mental correta para a utilização de semáforos: O mutex protege uma invariante. Quando o mutex é realizada, o invariante pode mudar, mas antes de liberar o mutex, o invariante é restabelecida. fechaduras de reentrada são perigosos porque a segunda vez que você adquirir o bloqueio você não pode ter certeza que a invariante é verdade mais.

Se você está feliz com fechaduras de reentrada, é só porque você não teve para depurar um problema como este antes. Java tem não reentrante bloqueia estes dias em java.util.concurrent.locks, pelo caminho.

Como escrito por Dave Butenhof se :

"O maior de todos os grandes problemas com mutexes recursiva é que eles incentivá-lo a controlar completamente perder de seu esquema de bloqueio e escopo. Esta é mortal. Mal. É o "comedor de thread". Você manter bloqueios para o absolutamente menor tempo possível. Período. Sempre. Se você está chamando algo com um bloqueio mantido simplesmente porque você não sabe é realizada, ou porque você não sabe se o receptor precisa do mutex, então você está segurá-lo por muito tempo. Você está apontando uma arma para sua aplicação e puxar o gatilho. Você provavelmente começou a usar tópicos para obter concorrência; mas você acabou de concorrência prevenida. "

O modelo mental correta para usar mutexes: O mutex protege um invariante.

Por que você está certo de que este é o modelo realmente certo mental para usar mutexes? Acho modelo certo é proteger os dados, mas não invariantes.

O problema de proteger invariantes presentes mesmo em aplicações single-threaded e tem comum nada com multi-threading e semáforos.

Além disso, se você precisa para proteger invariantes, você ainda pode usar semáforo binário wich nunca é recursiva.

Uma das principais razões que mutexes recursiva são úteis é no caso de se acessar os métodos várias vezes pelo mesmo thread. Por exemplo, digamos que se bloqueio mutex é proteger um banco de A / C para retirar, então se há uma taxa também associado com que a retirada, em seguida, a mesma extensão mútua tem de ser usado.

A única boa caso de uso para mutex recursão é quando um objeto contém vários métodos. Quando qualquer um dos métodos de modificar o conteúdo do objeto e, portanto, deve bloquear o objeto antes que o estado é consistente novamente.

Se os métodos usar outros métodos (ou seja: addNewArray () chama addNewPoint (), e finaliza com recheckBounds ()), mas qualquer uma dessas funções por si só precisa bloquear a exclusão mútua, então mutex recursiva é uma win-win.

Para qualquer outro caso (resolvendo apenas má codificação, usando-o mesmo em diferentes objetos) é claramente errado!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top