Eu sou novo no multithreading e estava tentando entender como os mutexes funcionam. Pesquisei bastante no Google, mas ainda havia algumas dúvidas de como funciona, porque criei meu próprio programa no qual o bloqueio não funcionava.
Uma sintaxe absolutamente não intuitiva do mutex é pthread_mutex_lock( &mutex1 );
, onde parece que o mutex está sendo bloqueado, quando o que realmente quero bloquear é outra variável. Essa sintaxe significa que o bloqueio de um mutex bloqueia uma região de código até que o mutex seja desbloqueado? Então, como os threads sabem que a região está bloqueada? [ UPDATE: Threads sabem que a região está bloqueada pelo Memory Fencing ]. E esse fenômeno não deveria ser chamado de seção crítica? [ ATUALIZAÇÃO: os objetos de seção crítica estão disponíveis apenas no Windows, onde os objetos são mais rápidos que os mutexes e são visíveis apenas ao encadeamento que o implementa. Caso contrário, a seção crítica se refere apenas à área do código protegida por um mutex ]
Em resumo, você poderia ajudar com o programa de exemplo mutex mais simples possível e com a explicação mais simples possível sobre a lógica de como ele funciona? Tenho certeza de que isso ajudará muitos outros novatos.
Respostas:
Aqui está minha humilde tentativa de explicar o conceito para iniciantes em todo o mundo: (uma versão com código de cores no meu blog também)
Muitas pessoas correm para uma cabine telefônica solitária (não têm celular) para conversar com seus entes queridos. A primeira pessoa a pegar a maçaneta da porta é quem tem permissão para usar o telefone. Ele tem que continuar segurando a maçaneta da porta enquanto usar o telefone, caso contrário, outra pessoa vai agarrar a maçaneta, jogá-lo fora e conversar com a esposa :) Não existe um sistema de filas como tal. Quando a pessoa termina a ligação, sai do estande e sai da maçaneta da porta, a próxima pessoa a segurar a maçaneta da porta poderá usar o telefone.
Um segmento é: Cada pessoa
O mutex é: A maçaneta da porta
A fechadura é: A mão da pessoa
O recurso é: O telefone
Qualquer encadeamento que precise executar algumas linhas de código que não devem ser modificadas por outros encadeamentos ao mesmo tempo (usando o telefone para conversar com a esposa), primeiro precisa adquirir uma trava em um mutex (segurando a maçaneta da porta do estande ) Somente então um thread poderá executar essas linhas de código (fazendo a ligação).
Depois que o segmento executar esse código, ele deverá liberar o bloqueio no mutex para que outro segmento possa adquirir um bloqueio no mutex (outras pessoas poderão acessar a cabine telefônica).
[ O conceito de ter um mutex é um pouco absurdo ao considerar o acesso exclusivo do mundo real, mas no mundo da programação acho que não havia outra maneira de permitir que os outros threads 'vissem' que um segmento já estava executando algumas linhas de código. Existem conceitos de mutexes recursivos, etc., mas este exemplo foi feito apenas para mostrar o conceito básico. Espero que o exemplo lhe dê uma imagem clara do conceito. ]
Com o segmento C ++ 11:
Compile e execute usando
g++ -std=c++0x -pthread -o thread thread.cpp;./thread
Em vez de usar explicitamente
lock
eunlock
, você pode usar colchetes como mostrado aqui , se estiver usando um bloqueio de escopo para a vantagem que ele oferece . Bloqueios com escopo definido têm uma ligeira sobrecarga de desempenho.fonte
(could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearer
. Quanto ao uso do bloqueio de escopo, cabe ao desenvolvedor, dependendo do tipo de aplicativo que eles estão criando. Esta resposta foi criada para abordar o entendimento básico do conceito de mutex e não entrar em todas as complexidades dele; portanto, seus comentários e links são bem-vindos, mas estão um pouco fora do escopo deste tutorial.Embora um mutex possa ser usado para resolver outros problemas, a principal razão pela qual eles existem é fornecer exclusão mútua e, assim, resolver o que é conhecido como condição de corrida. Quando dois (ou mais) threads ou processos tentam acessar a mesma variável simultaneamente, temos potencial para uma condição de corrida. Considere o seguinte código
O interior desta função parece tão simples. É apenas uma afirmação. No entanto, um equivalente típico da linguagem pseudo-assembly pode ser:
Como as instruções equivalentes em linguagem assembly são necessárias para executar a operação de incremento em i, dizemos que incrementar i é uma operação não atômica. Uma operação atômica é aquela que pode ser concluída no hardware com a garantia de não ser interrompida após o início da execução da instrução. Incrementar i consiste em uma cadeia de 3 instruções atômicas. Em um sistema simultâneo em que vários threads estão chamando a função, surgem problemas quando um thread lê ou grava na hora errada. Imagine que temos dois threads em execução simultaneoulsy e um chama a função imediatamente após o outro. Digamos também que eu inicializei com 0. Suponhamos também que temos muitos registros e que os dois threads estão usando registros completamente diferentes, portanto não haverá colisões. O tempo real desses eventos pode ser:
O que aconteceu é que temos dois threads incrementando i simultaneamente, nossa função é chamada duas vezes, mas o resultado é inconsistente com esse fato. Parece que a função foi chamada apenas uma vez. Isso ocorre porque a atomicidade é "interrompida" no nível da máquina, o que significa que os threads podem se interromper ou trabalhar juntos nos momentos errados.
Precisamos de um mecanismo para resolver isso. Precisamos impor alguns pedidos para as instruções acima. Um mecanismo comum é bloquear todos os threads, exceto um. Pthread mutex usa esse mecanismo.
Qualquer encadeamento que precise executar algumas linhas de código que possam modificar valores compartilhados por outros encadeamentos ao mesmo tempo (usando o telefone para conversar com sua esposa), deve primeiro fazer com que você adquira um bloqueio em um mutex. Dessa maneira, qualquer encadeamento que exija acesso aos dados compartilhados deve passar pelo bloqueio mutex. Somente então um thread poderá executar o código. Esta seção do código é chamada de seção crítica.
Depois que o encadeamento tiver executado a seção crítica, ele deverá liberar o bloqueio no mutex para que outro encadeamento possa adquirir um bloqueio no mutex.
O conceito de ter um mutex parece um pouco estranho quando consideramos seres humanos buscando acesso exclusivo a objetos físicos reais, mas ao programar, devemos ser intencionais. Os threads e processos simultâneos não têm a educação social e cultural que possuímos, portanto, devemos forçá-los a compartilhar dados de maneira adequada.
Então, tecnicamente falando, como um mutex funciona? Não sofre das mesmas condições raciais que mencionamos anteriormente? O pthread_mutex_lock () não é um pouco mais complexo que um simples incremento de uma variável?
Tecnicamente falando, precisamos de suporte de hardware para nos ajudar. Os projetistas de hardware nos dão instruções de máquina que fazem mais de uma coisa, mas são garantidas como atômicas. Um exemplo clássico de tal instrução é o teste e configuração (TAS). Ao tentar adquirir um bloqueio em um recurso, podemos usar o TAS para verificar se um valor na memória é 0. Se for, esse seria o nosso sinal de que o recurso está em uso e não fazemos nada (ou com mais precisão , esperamos por algum mecanismo. Um mutex pthreads nos colocará em uma fila especial no sistema operacional e nos notificará quando o recurso estiver disponível. Os sistemas mais difíceis podem exigir que façamos um loop de rotação apertado, testando a condição repetidamente) . Se o valor na memória não for 0, o TAS definirá o local para algo diferente de 0 sem usar outras instruções. Isto' é como combinar duas instruções de montagem em 1 para nos fornecer atomicidade. Portanto, o teste e a alteração do valor (se a alteração for apropriada) não podem ser interrompidos após o início. Podemos construir mutexes sobre essa instrução.
Nota: algumas seções podem parecer semelhantes a uma resposta anterior. Eu aceitei o convite dele para editar, ele preferiu o jeito original, então eu estou mantendo o que eu tinha, que é infundido com um pouco de sua verborragia.
fonte
O melhor tutorial de tópicos que eu conheço está aqui:
https://computing.llnl.gov/tutorials/pthreads/
Gosto que ele seja escrito sobre a API, e não sobre uma implementação específica, e fornece alguns bons exemplos simples para ajudar você a entender a sincronização.
fonte
Encontrei este post recentemente e acho que ele precisa de uma solução atualizada para o mutex c ++ 11 da biblioteca padrão (ou seja, std :: mutex).
Eu colei alguns códigos abaixo (meus primeiros passos com um mutex - eu aprendi simultaneidade no win32 com HANDLE, SetEvent, WaitForMultipleObjects etc).
Como é minha primeira tentativa com std :: mutex e amigos, adoraria ver comentários, sugestões e melhorias!
fonte
A função
pthread_mutex_lock()
quer adquire o mutex para o segmento de chamada ou bloqueia o segmento até que o mutex pode ser adquirido. O relacionadopthread_mutex_unlock()
libera o mutex.Pense no mutex como uma fila; todo encadeamento que tentar adquirir o mutex será colocado no final da fila. Quando um encadeamento libera o mutex, o próximo encadeamento na fila sai e agora está em execução.
Uma seção crítica refere-se a uma região de código onde o não-determinismo é possível. Geralmente, isso ocorre porque vários threads estão tentando acessar uma variável compartilhada. A seção crítica não é segura até que algum tipo de sincronização esteja em vigor. Um bloqueio mutex é uma forma de sincronização.
fonte
Você deve verificar a variável mutex antes de usar a área protegida pelo mutex. Portanto, seu pthread_mutex_lock () pode (dependendo da implementação) esperar até o mutex1 ser liberado ou retornar um valor indicando que o bloqueio não pode ser obtido se alguém já o tiver bloqueado.
O Mutex é realmente apenas um semáforo simplificado. Se você ler sobre eles e entendê-los, entenderá mutexes. Existem várias perguntas sobre mutexes e semáforos no SO. Diferença entre o semáforo binário e o mutex , quando devemos usar o mutex e quando devemos usar o semáforo e assim por diante. O exemplo do banheiro no primeiro link é um exemplo tão bom quanto se pode imaginar. Todo o código faz é verificar se a chave está disponível e, se estiver, reserva-a. Observe que você realmente não reserva o banheiro em si, mas a chave.
fonte
pthread_mutex_lock
não pode retornar se outra pessoa segurar a trava. Bloqueia neste caso e esse é o ponto.pthread_mutex_trylock
é a função que retornará se o bloqueio for mantido.Para quem procura o exemplo mutex do shortex:
fonte
EXEMPLO SEMÁFORO ::
Referência: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt
fonte