Eu acho que os dois estão fazendo o mesmo trabalho, como você decide qual deles usar para sincronização?
Eu acho que os dois estão fazendo o mesmo trabalho, como você decide qual deles usar para sincronização?
Respostas:
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,
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.
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
& mutexes
pode 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.
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
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.
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).
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!