Quando se deve usar um semáforo e quando se deve usar uma variável condicional (CondVar)?
Informações relacionadas também podem ser encontradas no link stackoverflow.com/questions/4039899/…
Os bloqueios são usados ​​para exclusão mútua. Quando você quiser garantir que um trecho de código seja atômico, coloque um cadeado em torno dele. Você poderia teoricamente usar um semáforo binário para fazer isso, mas esse é um caso especial.
Semáforos e variáveis ​​de condição são construídos sobre a exclusão mútua fornecida por bloqueios e são usados ​​para fornecer acesso sincronizado a recursos compartilhados. Eles podem ser usados ​​para fins semelhantes.
Uma variável de condição é geralmente usada para evitar a espera ocupada (repetindo repetidamente durante a verificação de uma condição) enquanto aguarda a disponibilização de um recurso. Por exemplo, se você tiver um thread (ou vários threads) que não pode continuar até que uma fila esteja vazia, a abordagem de espera ocupada seria apenas fazer algo como:
O problema com isso é que você está desperdiçando tempo do processador ao fazer com que esse thread verifique repetidamente a condição. Por que não ter uma variável de sincronização que pode ser sinalizada para informar ao thread que o recurso está disponível?
Presumivelmente, você terá um tópico em outro lugar que está retirando coisas da fila. Quando a fila está vazia, ele pode chamar syncVar.signal()para despertar um thread aleatório que está adormecido syncVar.wait()(ou geralmente também há um método signalAll()ou broadcast()para despertar todos os threads que estão esperando).
Eu geralmente uso variáveis ​​de sincronização como essa quando tenho um ou mais threads aguardando uma única condição específica (por exemplo, para a fila ficar vazia).
Os semáforos podem ser usados ​​de forma semelhante, mas acho que eles são mais bem usados ​​quando você tem um recurso compartilhado que pode estar disponível e indisponível com base em um número inteiro de coisas disponíveis. Os semáforos são bons para situações de produtor / consumidor em que os produtores estão alocando recursos e os consumidores os consumindo.
Pense se você tivesse uma máquina de venda automática de refrigerantes. Existe apenas uma máquina de refrigerante e é um recurso compartilhado. Você tem um segmento que é um fornecedor (produtor) responsável por manter a máquina estocada e N segmentos que são compradores (consumidores) que desejam obter refrigerantes da máquina. O número de refrigerantes na máquina é o valor inteiro que irá direcionar nosso semáforo.
Cada segmento de comprador (consumidor) que chega à máquina de refrigerante chama o down()método do semáforo para pegar um refrigerante. Isso pegará um refrigerante da máquina e diminuirá a contagem de refrigerantes disponíveis em 1. Se houver refrigerantes disponíveis, o código continuará executando além da down()instrução sem problemas. Se não houver refrigerantes disponíveis, o tópico vai dormir aqui esperando ser notificado quando o refrigerante for disponibilizado novamente (quando houver mais refrigerantes na máquina).
O segmento do fornecedor (produtor) estaria essencialmente esperando que a máquina de refrigerante se esvaziasse. O vendedor é notificado quando o último refrigerante é retirado da máquina (e um ou mais consumidores estão potencialmente esperando para retirar os refrigerantes). O vendedor reabasteceria a máquina de refrigerante com o up()método do semáforo , o número disponível de refrigerantes seria incrementado a cada vez e, assim, os fios do consumidor em espera seriam notificados de que mais refrigerante está disponível.
Os métodos wait()e signal()de uma variável de sincronização tendem a ficar ocultos nas operações down()e up()do semáforo.
Certamente há sobreposição entre as duas opções. Existem muitos cenários onde um semáforo ou uma variável de condição (ou conjunto de variáveis ​​de condição) podem servir aos seus propósitos. Os semáforos e as variáveis ​​de condição são associados a um objeto de bloqueio que eles usam para manter a exclusão mútua, mas fornecem funcionalidade extra no topo do bloqueio para sincronizar a execução do thread. Depende principalmente de você descobrir qual deles faz mais sentido para a sua situação.
Essa não é necessariamente a descrição mais técnica, mas é assim que faz sentido na minha cabeça.
Ótima resposta, eu gostaria de acrescentar de outras respostas: Semaphore é usado para controlar o número de threads em execução. Haverá um conjunto fixo de recursos. A contagem de recursos será diminuída toda vez que um thread possuir o mesmo. Quando a contagem do semáforo chega a 0, nenhum outro encadeamento pode adquirir o recurso. As threads são bloqueadas até que outras threads possuam recursos. Resumindo, a principal diferença é quantos threads têm permissão para adquirir o recurso de uma vez? Mutex - seu ONE. Semáforo - é DEFINED_COUNT, (tantos quanto a contagem do semáforo)
Só para explicar por que existe esse loop while em vez de um if simples: algo chamado despertar spurios . Citando este artigo da Wikipédia : "Uma das razões para isso é um despertar espúrio; isto é, um thread pode ser despertado de seu estado de espera, mesmo que nenhum thread tenha sinalizado a variável de condição"
— Vladislavs Burakovs
@VladislavsBurakovs Bom argumento! Acho que também é útil para o caso em que uma transmissão desperta mais threads do que os recursos disponíveis (por exemplo, a transmissão desperta 3 threads, mas há apenas 2 itens na fila).
Gostaria de votar positivamente em sua resposta até que a fila esteja cheia;) Resposta perfeita. Este código pode ajudar a descobrir os semáforos csc.villanova.edu/~mdamian/threads/PC.htm
@VladislavsBurakovs Para esclarecer um pouco, o motivo pelo qual a condição ainda pode ser falsa para um thread que acabou de ser ativado (resultando em uma ativação falsa) é que pode ter ocorrido uma troca de contexto antes que o thread tivesse a chance de verificar a condição novamente, onde algum outro encadeamento programado tornou essa condição falsa. Este é um dos motivos que conheço para um despertar espúrio, não sei se há mais.
Vamos revelar o que está por baixo do capô.
A variável condicional é essencialmente uma fila de espera , que suporta operações de espera de bloqueio e ativação, ou seja, você pode colocar uma thread na fila de espera e definir seu estado como BLOCK, e obter uma thread dela e definir seu estado como READY.
Observe que para usar uma variável condicional, dois outros elementos são necessários:
uma condição (normalmente implementada pela verificação de um sinalizador ou contador)
um mutex que protege a condição
O protocolo então se torna,
verificar condição
bloquear e liberar o mutex se a condição for verdadeira, caso contrário, libere o mutex
O semáforo é essencialmente um contador + um mutex + uma fila de espera. E pode ser usado como está, sem dependências externas. Você pode usá-lo como um mutex ou como uma variável condicional.
Portanto, o semáforo pode ser tratado como uma estrutura mais sofisticada do que a variável condicional, enquanto a última é mais leve e flexível.
— cucufrog
mutex pode ser visto como uma variável de condições, sua condição é ser mantida ou não.
Os semáforos podem ser usados ​​para implementar acesso exclusivo a variáveis, no entanto, eles devem ser usados ​​para sincronização. Mutexes, por outro lado, têm uma semântica estritamente relacionada à exclusão mútua: apenas o processo que bloqueou o recurso pode desbloqueá-lo.
Infelizmente você não pode implementar a sincronização com mutexes, é por isso que temos variáveis ​​de condição. Observe também que, com variáveis ​​de condição, você pode desbloquear todos os threads em espera no mesmo instante usando o desbloqueio de transmissão. Isso não pode ser feito com semáforos.
variáveis ​​de semáforo e condição são muito semelhantes e são usadas principalmente para os mesmos fins. No entanto, existem pequenas diferenças que podem tornar um preferível. Por exemplo, para implementar a sincronização de barreira, você não seria capaz de usar um semáforo. Mas uma variável de condição é ideal.
A sincronização de barreira é quando você deseja que todos os seus threads esperem até que todos tenham chegado a uma determinada parte da função de thread. isso pode ser implementado por ter uma variável estática que é inicialmente o valor do total de threads diminuída por cada thread quando atinge essa barreira. isso significaria que queremos que cada thread durma até que o último chegue. Um semáforo faria exatamente o oposto! com um semáforo, cada thread continuará em execução e o último thread (que definirá o valor do semáforo para 0) irá dormir.
uma variável de condição, por outro lado, é ideal. quando cada thread chega à barreira, verificamos se nosso contador estático é zero. se não, configuramos o thread para hibernar com a função de espera da variável de condição. quando a última thread chegar na barreira, o valor do contador será decrementado para zero e esta última thread irá chamar a função de sinal da variável de condição que irá despertar todas as outras threads!
Eu arquivo variáveis ​​de condição sob sincronização de monitor. Geralmente, vejo semáforos e monitores como dois estilos de sincronização diferentes. Existem diferenças entre os dois em termos de quantos dados de estado são inerentemente mantidos e como você deseja modelar o código - mas realmente não há nenhum problema que possa ser resolvido por um, mas não pelo outro.
Eu tendo a codificar para a forma de monitor; na maioria das linguagens que trabalho, isso se resume a mutexes, variáveis ​​de condição e algumas variáveis ​​de estado de apoio. Mas os semáforos também fariam o trabalho.
Esta seria uma resposta melhor se você explicasse o que é "forma de monitor".
O mutexe conditional variablessão herdados de semaphore.
Pois mutex, o semaphoreusa dois estados: 0, 1
Para condition variableso semaphore contador de usos.
Eles são como açúcar sintático
Na biblioteca std C ++, eles são todos objetos distritais, todos implementados usando APIs específicas da plataforma. Certamente um semáforo irá desbloquear o número de vezes que sinalizado, a variável de condição pode ser sinalizada várias vezes, mas desbloquear apenas uma vez. É por isso que o wair usa um mutex como parâmetro.