Quando alguém deve usar um spinlock em vez de mutex?

294

Eu acho que os dois estão fazendo o mesmo trabalho, como você decide qual deles usar para sincronização?

compilar-fã
fonte
1
possível duplicata do Spinlock Versus Semaphore!
Paul R
11
Mutex e Semaphore não são a mesma coisa, então não acho que seja uma duplicata. A resposta do artigo mencionado afirma isso corretamente. Para mais detalhes veja barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore
nanoquack

Respostas:

729

A teoria

Em teoria, quando um thread tenta bloquear um mutex e não obtém êxito, porque o mutex já está bloqueado, ele entra no modo de suspensão, permitindo imediatamente que outro thread seja executado. Ele continuará em suspensão até ser acordado, o que acontecerá assim que o mutex estiver sendo desbloqueado por qualquer thread que estava segurando a trava antes. Quando um encadeamento tenta bloquear um spinlock e ele não obtém êxito, ele tenta repetidamente bloqueá-lo até que finalmente seja bem-sucedido; portanto, não permitirá que outro encadeamento ocorra (no entanto, o sistema operacional mudará forçosamente para outro encadeamento, uma vez que o quantum de tempo de execução da CPU do encadeamento atual tenha sido excedido, é claro).

O problema

O problema dos mutexes é que colocar os threads em suspensão e reativá-los são operações bastante caras, elas precisam de muitas instruções da CPU e, portanto, também levam algum tempo. Se agora o mutex foi bloqueado apenas por um período muito curto, o tempo gasto em colocar um thread em suspensão e em ativá-lo novamente pode exceder o tempo em que o thread realmente dormiu de longe e pode até exceder o tempo que o thread duraria desperdiçado constantemente pesquisando um spinlock. Por outro lado, a pesquisa em um spinlock desperdiçará constantemente tempo da CPU e, se o bloqueio for mantido por um período maior, isso desperdiçará muito mais tempo da CPU e teria sido muito melhor se o encadeamento estivesse dormindo.

A solução

O uso de spinlocks em um sistema single-core / single-CPU geralmente não faz sentido, desde que a pesquisa de spinlock esteja bloqueando o único núcleo disponível da CPU, nenhum outro encadeamento poderá ser executado e, como nenhum outro encadeamento, o bloqueio não funcionará. ser desbloqueado também. IOW, um spinlock desperdiça apenas o tempo da CPU nesses sistemas sem nenhum benefício real. Se o encadeamento foi colocado no modo de suspensão, outro encadeamento poderia ter sido executado ao mesmo tempo, possivelmente desbloqueando a trava e permitindo que o primeiro encadeamento continuasse o processamento, uma vez que acordasse novamente.

Em sistemas com vários núcleos / várias CPUs, com muitos bloqueios mantidos por um período muito curto, o tempo desperdiçado para colocar os threads em suspensão constantemente e ativá-los novamente pode diminuir visivelmente o desempenho do tempo de execução. Ao usar spinlocks, os threads têm a chance de tirar proveito de seu quantum de tempo de execução completo (sempre bloqueando apenas por um período muito curto, mas continuando imediatamente seu trabalho), levando a uma taxa de transferência de processamento muito maior.

A prática

Como muitas vezes os programadores não podem saber com antecedência se mutexes ou spinlocks serão melhores (por exemplo, porque o número de núcleos de CPU da arquitetura de destino é desconhecido), nem os sistemas operacionais podem saber se um determinado trecho de código foi otimizado para núcleo único ou Em ambientes com vários núcleos, a maioria dos sistemas não distingue estritamente entre mutexes e spinlocks. De fato, os sistemas operacionais mais modernos têm mutexes híbridos e spinlocks híbridos. O que isso realmente significa?

Um mutex híbrido se comporta como um spinlock inicialmente em um sistema com vários núcleos. Se um segmento não puder bloquear o mutex, ele não será interrompido imediatamente, pois o mutex poderá ser desbloqueado muito em breve; portanto, o mutex primeiro se comportará exatamente como um spinlock. Somente se a trava ainda não tiver sido obtida após um certo período de tempo (ou novas tentativas ou qualquer outro fator de medição), o encadeamento será realmente adormecido. Se o mesmo código for executado em um sistema com apenas um único núcleo, o mutex não funcionará, embora, como veja acima, isso não seja benéfico.

Um spinlock híbrido se comporta como um spinlock normal no início, mas, para evitar desperdiçar muito tempo de CPU, pode ter uma estratégia de retirada. Geralmente, ele não coloca o thread em suspensão (já que você não deseja que isso aconteça ao usar um spinlock), mas pode decidir interromper o thread (imediatamente ou após um certo período de tempo) e permitir que outro thread seja executado , aumentando assim as chances de que o spinlock seja desbloqueado (uma opção de thread puro geralmente é menos cara do que aquela que envolve colocar um thread em suspensão e ativá-lo novamente mais tarde, embora não de longe).

Resumo

Em caso de dúvida, use mutexes, eles geralmente são a melhor escolha e os sistemas mais modernos permitirão que eles girem por um período muito curto, se isso parecer benéfico. Às vezes, o uso de spinlocks pode melhorar o desempenho, mas apenas sob certas condições, e o fato de você estar em dúvida me diz que você não está trabalhando em nenhum projeto atualmente em que um spinlock possa ser benéfico. Você pode considerar usar seu próprio "objeto de bloqueio", que pode usar um spinlock ou um mutex internamente (por exemplo, esse comportamento pode ser configurável ao criar esse objeto), inicialmente use mutexes em qualquer lugar e se você acha que usar um spinlock em algum lugar pode realmente ajuda, tente e compare os resultados (por exemplo, usando um criador de perfil), mas não deixe de testar os dois casos,

Atualização: um aviso para iOS

Na verdade, não é específico do iOS, mas o iOS é a plataforma na qual a maioria dos desenvolvedores pode enfrentar esse problema: se o seu sistema tiver um agendador de encadeamentos, isso não garante que nenhum encadeamento, por menor que seja sua prioridade, terá a chance de ser executado, os spinlocks podem levar a bloqueios permanentes. O agendador do iOS distingue diferentes classes de threads e os threads em uma classe inferior serão executados apenas se nenhum segmento em uma classe superior desejar executar também. Não há uma estratégia de retirada para isso, portanto, se você tiver permanentemente threads de alta classe disponíveis, os threads de baixa classe nunca terão tempo de CPU e, portanto, nunca terão chance de executar qualquer trabalho.

O problema aparece da seguinte maneira: Seu código obtém um spinlock em um segmento de classe prio baixa e, enquanto estiver no meio desse bloqueio, o quantum de tempo excedeu e o segmento para de executar. A única maneira como esse spinlock pode ser liberado novamente é se esse segmento de classe prio baixa tiver tempo de CPU novamente, mas isso não é garantido. Você pode ter alguns threads de classe prio alta que desejam constantemente executar e o agendador de tarefas sempre os priorizará. Um deles pode atravessar o spinlock e tentar obtê-lo, o que não é possível, é claro, e o sistema fará com que ele ceda. O problema é: Um encadeamento que produziu está imediatamente disponível para execução novamente! Tendo um prio maior do que o encadeamento que trava a trava, o encadeamento que trava a trava não tem chance de obter o tempo de execução da CPU.

Por que esse problema não ocorre com mutexes? Quando o encadeamento high prio não pode obter o mutex, ele não produz, ele pode girar um pouco, mas será enviado para dormir. Um encadeamento adormecido não está disponível para execução até ser acordado por um evento, por exemplo, um evento como o mutex sendo desbloqueado pelo qual estava aguardando. A Apple está ciente desse problema e, portanto, ficou obsoleta OSSpinLock. O novo bloqueio é chamado os_unfair_lock. Esse bloqueio evita a situação mencionada acima, pois está ciente das diferentes classes de prioridade do encadeamento. Se você tem certeza de que usar spinlocks é uma boa ideia no seu projeto iOS, use esse. Fique longe deOSSpinLock! E, sob nenhuma circunstância, implemente seus próprios spinlocks no iOS! Em caso de dúvida, use um mutex! O macOS não é afetado por esse problema, pois possui um agendador de encadeamentos diferente que não permite que nenhum encadeamento (mesmo encadeamentos prio baixos) "seque" no tempo da CPU, ainda assim a mesma situação pode surgir e levar a muito problemas desempenho, portanto, OSSpinLocké preterido no macOS também.

Mecki
fonte
3
excelente explicação ... Tenho uma dúvida sobre o spinlock i, e posso usar um spinlock no ISR? se não, por que não
haris 31/03
4
@Mecki Se não me engano, acredito que você sugeriu na sua resposta que a redução do tempo ocorre apenas em sistemas de processador único. Isso não está correto! Você pode usar um bloqueio de rotação em um sistema de processador único e ele girará até que seu quantum de tempo expire. Em seguida, outro segmento da mesma prioridade pode assumir o controle (exatamente como o que você descreveu para sistemas com vários processadores).
precisa saber é o seguinte
7
@ fumoboy007 "e ele girará até o tempo quântico expirar" // O que significa que você desperdiça tempo de CPU / energia da bateria por absolutamente nada sem qualquer benefício único, o que é absolutamente idiota. E não, em nenhum lugar eu disse que a divisão do tempo só acontece em sistemas de núcleo único; eu disse que em sistemas de núcleo único existe apenas a divisão de tempo, enquanto existe um paralelismo REAL de sistemas multicore (e também a divisão do tempo, ainda que irrelevante para o que escrevi em meu artigo). resposta); você também não entendeu o que é um spinlock híbrido e por que ele funciona bem em sistemas únicos e multicore.
Mecki #
11
@ fumoboy007 O segmento A mantém a trava e é interrompido. O segmento B é executado e deseja o bloqueio, mas não pode obtê-lo, portanto ele gira. Em um sistema multicore, o Thread A pode continuar em execução em outro núcleo enquanto o Thread B ainda está girando, libera o bloqueio e o Thread B pode continuar dentro do seu quantum de tempo atual. Em um sistema de núcleo único, existe apenas um segmento que o Thread A pode executar para liberar o bloqueio e esse núcleo é mantido ocupado pela rotação do Thread B. Portanto, não há como o spinlock jamais ser liberado antes que o Thread B exceda seu quantum de tempo e, portanto, todo spinning é apenas uma perda de tempo.
Mecki
2
Se você quiser aprender mais sobre spinlocks e mutexes implementados no kernel Linux, recomendo a leitura do capítulo 5 de ótimos Linux Device Drivers, Terceira Edição (LDD3) (mutexes: página 109; spinlocks: página 116).
patryk.beza
7

Continuando com a sugestão de Mecki, este artigo pthread mutex vs pthread spinlock no blog de Alexander Sandler, Alex no Linux mostra como o spinlock& mutexespode ser implementado para testar o comportamento usando #ifdef.

No entanto, certifique-se de atender a chamada final com base em sua observação, pois o exemplo dado é um caso isolado, seu requisito de projeto, ambiente pode ser totalmente diferente.

TheCottonSilk
fonte
6

Observe também que em determinados ambientes e condições (como a execução em janelas no nível de despacho> = NÍVEL DE EXPEDIÇÃO), você não pode usar o mutex, mas o spinlock. No unix - a mesma coisa.

Aqui está uma pergunta equivalente no site do concorrente stackexchange unix: /unix/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something- Mais

Informações sobre o envio em sistemas Windows: http://download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc

Dan Jobs
fonte
6

A resposta de Mecki é muito boa. No entanto, em um único processador, o uso de um spinlock pode fazer sentido quando a tarefa estiver aguardando que o bloqueio seja fornecido por uma Rotina de Serviço de Interrupção. A interrupção transferiria o controle para o ISR, que prepararia o recurso para uso pela tarefa em espera. Terminaria liberando a trava antes de devolver o controle à tarefa interrompida. A tarefa de rotação encontraria o spinlock disponível e continuaria.

AlanC
fonte
2
Não tenho certeza de que concordo totalmente com esta resposta. Em um único processador, se uma tarefa retém um bloqueio em um recurso, o ISR não pode prosseguir com segurança e não pode esperar que a tarefa desbloqueie o recurso (desde que a tarefa que continha o recurso foi interrompida). Nesses casos, a tarefa deve simplesmente desativar as interrupções para impor a exclusão entre ela mesma e o ISR. Obviamente, isso teria que ser feito por intervalos de tempo muito curtos.
user1202136
3

Hoje, os mecanismos de sincronização Spinlock e Mutex são muito comuns.

Vamos pensar no Spinlock primeiro.

Basicamente, é uma ação de espera ocupada, o que significa que precisamos aguardar a liberação de um bloqueio especificado antes de prosseguir com a próxima ação. Conceitualmente muito simples, enquanto implementá-lo não é o caso. Por exemplo: Se o bloqueio não foi liberado, o thread foi trocado e entrou no estado de suspensão, devemos lidar com isso? Como lidar com bloqueios de sincronização quando dois threads solicitam acesso simultaneamente?

Geralmente, a idéia mais intuitiva é lidar com a sincronização por meio de uma variável para proteger a seção crítica. O conceito de Mutex é semelhante, mas eles ainda são diferentes. Concentre-se em: utilização da CPU. O Spinlock consome tempo da CPU para aguardar a ação e, portanto, podemos resumir a diferença entre os dois:

Em ambientes homogêneos com vários núcleos, se o tempo gasto na seção crítica for pequeno do que usar o Spinlock, porque podemos reduzir o tempo de troca de contexto. (A comparação de núcleo único não é importante, porque alguns sistemas implementam o Spinlock no meio do comutador)

No Windows, o uso do Spinlock atualizará o thread para DISPATCH_LEVEL, o que, em alguns casos, pode não ser permitido, portanto, desta vez, tivemos que usar um Mutex (APC_LEVEL).

Marcus Thornton
fonte
-6

O uso de spinlocks em um sistema single-core / single-CPU geralmente não faz sentido, desde que a pesquisa de spinlock esteja bloqueando o único núcleo disponível da CPU, nenhum outro encadeamento poderá ser executado e, como nenhum outro encadeamento, o bloqueio não funcionará. ser desbloqueado também. IOW, um spinlock desperdiça apenas o tempo da CPU nesses sistemas sem nenhum benefício real

Isto está errado. Não há desperdício de ciclos da CPU no uso de spinlocks em sistemas de processador uni, porque uma vez que um processo executa um bloqueio de rotação, a preempção é desativada; portanto, não há mais ninguém girando! Só que usá-lo não faz sentido! Portanto, spinlocks nos sistemas Uni são substituídos por preempt_disable em tempo de compilação pelo kernel!

Neelansh Mittal
fonte
O citado ainda é inteiramente verdadeiro. Se o resultado compilado do código-fonte não contiver spinlocks, o citado é irrelevante. Supondo que o que você disse é verdade sobre o kernel substituir os spinlocks no tempo de compilação, como os spinlocks são manipulados quando pré-compilados em outra máquina que pode ou não ser uniprocessadora, a menos que falemos estritamente sobre spinlocks apenas no próprio kernel?
Hydranix
"Depois que um processo executa um bloqueio de rotação, a preempção é desativada". A preempção não é desativada quando um processo gira. Se fosse esse o caso, um único processo poderia derrubar a máquina inteira, basta digitar um spinlock e nunca sair. Observe que, se o seu thread for executado no espaço do kernel (em vez do espaço do usuário), realizar um bloqueio de rotação realmente desabilita a preempção, mas acho que não é isso que está sendo discutido aqui.
Konstantin Weitz
Em tempo de compilação pelo kernel ?
Shien
@konstantin O bloqueio de rotação FYI só pode ser obtido no espaço do kernel. E quando um bloqueio de rotação é realizado, a preempção é desativada no processador local.
Neelansh Mittal
Obviamente, você não pode compilar um módulo em um kernel no qual o CONFIG_SMP está ativado e executar o mesmo módulo em um kernel que o CONFIG_SMP está desabilitado.
Neelansh Mittal