Bloqueio, mutex, semáforo ... qual é a diferença?


Respostas:


533

Um bloqueio permite que apenas um encadeamento entre na peça que está bloqueada e o bloqueio não é compartilhado com nenhum outro processo.

Um mutex é igual a um bloqueio, mas pode ser amplo no sistema (compartilhado por vários processos).

Um semáforo faz o mesmo que um mutex, mas permite a entrada de um número x de threads; isso pode ser usado, por exemplo, para limitar o número de tarefas intensivas de CPU, io ou ram em execução ao mesmo tempo.

Para um post mais detalhado sobre as diferenças entre mutex e semáforo, leia aqui .

Você também tem bloqueios de leitura / gravação que permitem um número ilimitado de leitores ou um escritor a qualquer momento.


2
@mertinan, não posso dizer que já ouvi falar sobre isso, mas é isso que a wikipedia diz "Trava (banco de dados), (uma vida relativamente curta) trava em uma estrutura de dados do sistema como um índice"
Peter

2
O monitor permite aguardar uma determinada condição (por exemplo, quando a trava é liberada), "monitora".
Dzmitry Lazerka

25
Um semáforo não é o mesmo que um mutex. Eles são usados ​​de maneira muito diferente e também possuem propriedades diferentes (principalmente em relação à propriedade). Ver, por exemplo barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore para mais detalhes
nanoquack

3
@ nanoquack fique à vontade para editar minha resposta se você achar que ela é enganosa ou incorreta.
Peter

3
Para uma distinção mais clara entre mutex e semáforo, no link do nanoquack, o parágrafo principal é " O uso correto de um semáforo é para sinalizar de uma tarefa para outra. Um mutex deve ser usado e liberado, sempre nessa ordem, por cada tarefa que usa o recurso compartilhado que protege por outro lado, as tarefas que usam semáforos qualquer sinal ou espera-não ambos.. "
ToolmakerSteve

117

Existem muitos conceitos errados sobre essas palavras.

Isso é de uma postagem anterior ( https://stackoverflow.com/a/24582076/3163691 ) que se encaixa perfeitamente aqui:

1) Seção Crítica = Objeto de usuário usado para permitir a execução de apenas um encadeamento ativo de muitos outros em um processo . Os outros threads não selecionados (@ adquirindo este objeto) são colocados no modo de suspensão .

[Sem capacidade de interprocesso, objeto muito primitivo].

2) Mutex Semáforo (também conhecido como Mutex) = Objeto do kernel usado para permitir a execução de apenas um encadeamento ativo de muitos outros, entre diferentes processos . Os outros threads não selecionados (@ adquirindo este objeto) são colocados no modo de suspensão . Este objeto suporta propriedade de thread, notificação de encerramento de thread, recursão (várias chamadas de 'aquisição' do mesmo thread) e 'prevenção de inversão de prioridade'.

[Capacidade entre processos, muito segura de usar, um tipo de objeto de sincronização de 'alto nível'].

3) Counting Semaphore (aka Semaphore) = Objeto do kernel usado para permitir a execução de um grupo de threads ativos de muitos outros. Os outros threads não selecionados (@ adquirindo este objeto) são colocados no modo de suspensão .

[Contudo, a capacidade de interprocesso não é muito segura de usar porque não possui os seguintes atributos 'mutex': notificação de encerramento de encadeamento, recursão ?, 'prevenção de inversão de prioridade' ?, etc].

4) E agora, falando sobre 'spinlocks', primeiro algumas definições:

Região crítica = Uma região de memória compartilhada por 2 ou mais processos.

Lock = Uma variável cujo valor permite ou nega a entrada em uma 'região crítica'. (Pode ser implementado como um simples 'sinalizador booleano').

Espera ocupada = Teste contínuo de uma variável até que algum valor apareça.

Finalmente:

Spin-lock (aka Spinlock) = Um bloqueio que usa a espera ocupada . (A aquisição do bloqueio é feita por xchg ou operações atômicas similares ).

[Sem thread em suspensão, usada principalmente apenas no nível do kernel. Ineficiente para o código no nível do usuário].

Como último comentário, não tenho certeza, mas posso apostar muito dinheiro que os três primeiros objetos de sincronização acima (# 1, # 2 e # 3) fazem uso dessa besta simples (# 4) como parte de sua implementação.

Tenha um bom dia!.

Referências:

Conceitos em tempo real para sistemas embarcados por Qing Li com Caroline Yao (CMP Books).

- Sistemas operacionais modernos (3º) por Andrew Tanenbaum (Pearson Education International).

Aplicativos de programação para Microsoft Windows (4º) por Jeffrey Richter (Microsoft Programming Series).

Além disso, você pode dar uma olhada em: https://stackoverflow.com/a/24586803/3163691


1
A seção realmente crítica não é um objeto do kernel, portanto, mais leve e incapaz de sincronizar os processos.
Vladislavs Burakovs

2
@ Vladislavs Burakovs: Você está certo! Perdoe minha redação. Vou consertar isso por uma questão de coerência.
fante

Para obter uma distinção mais clara entre mutex e semáforo, como o nanoquack menciona em outros lugares, consulte barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore - O parágrafo principal é " O uso correto de um semáforo é para sinalizar de uma tarefa para outro. Um mutex deve ser obtido e liberado, sempre nessa ordem, por cada tarefa que usa o recurso compartilhado que protege. Por outro lado, as tarefas que usam semáforos sinalizam ou esperam - e não ambos. "
ToolmakerSteve

Re-conjecture outros mecanismos de bloqueio baseados no spinlock [ineficiente]: improvável; O AFAIK precisa apenas de algumas operações atômicas, além de filas de suspensão. Mesmo onde o spinlock é necessário dentro do kernel, as soluções modernas minimizam seu impacto, conforme descrito em Wikipedia - Spinlock - Alternatives - " .. use uma abordagem híbrida chamada" mutex adaptável ". A idéia é usar um spinlock ao tentar acessar um recurso bloqueado por um fio actualmente em execução, mas para dormir, se o segmento não está sendo executado (o último é sempre o caso em sistemas de processador único.). "
ToolmakerSteve

@ToolmakerSteve, eu desafio você a fornecer uma 'solução' sem um 'spinlock' para o problema de 'colisões' ao tentar 'inserir' um ID de thread em uma 'fila de espera'. De qualquer forma, o texto da Wikipedia conclui que um spinlock é usado na implementação !!!.
fante

27

A maioria dos problemas pode ser resolvida usando (i) apenas travas, (ii) apenas semáforos, ... ou (iii) uma combinação de ambos! Como você deve ter descoberto, eles são muito parecidos: ambos impedem condições de corrida , ambos têm acquire()/ release()operações, causam zero ou mais threads a serem bloqueados / suspeitos ... Realmente, a diferença crucial reside apenas em como eles bloqueiam e desbloqueiam .

  • Um bloqueio (ou mutex ) possui dois estados (0 ou 1). Pode ser desbloqueado ou bloqueado . Eles geralmente são usados ​​para garantir que apenas um thread entre em uma seção crítica por vez.
  • Um semáforo tem muitos estados (0, 1, 2, ...). Pode ser bloqueado (estado 0) ou desbloqueado (estados 1, 2, 3, ...). Um ou mais semáforos são frequentemente usados ​​juntos para garantir que apenas um encadeamento entre em uma seção crítica precisamente quando o número de unidades de algum recurso atingiu / não atingiu um valor específico (seja através da contagem decrescente para esse valor ou contando até esse valor )

Para os dois bloqueios / semáforos, tentar chamar acquire()enquanto o primitivo está no estado 0 faz com que o encadeamento de chamada seja suspenso. Para bloqueios - as tentativas de adquirir o bloqueio estão no estado 1 com êxito. Para semáforos - as tentativas de obter o bloqueio nos estados {1, 2, 3, ...} foram bem-sucedidas.

Para bloqueios no estado 0, se o mesmo encadeamento chamado anteriormente acquire(), agora chamar release, o release será bem-sucedido. Se um thread diferente tentou isso - cabe à implementação / biblioteca o que acontece (geralmente a tentativa é ignorada ou ocorre um erro). Para semáforos no estado 0, qualquer encadeamento pode chamar release e será bem-sucedido (independentemente de qual encadeamento usado anteriormente adquirir para colocar o semáforo no estado 0).

Da discussão anterior, podemos ver que os bloqueios têm uma noção de proprietário (o único encadeamento que pode chamar a liberação é o proprietário), enquanto os semáforos não têm um proprietário (qualquer encadeamento pode chamar a liberação em um semáforo).


O que causa muita confusão é que, na prática, existem muitas variações dessa definição de alto nível.

Variações importantes a serem consideradas :

  • Como deve ser chamado acquire()/ release()? - [Varia massivamente ]
  • Seu bloqueio / semáforo usa uma "fila" ou um "conjunto" para lembrar os threads em espera?
  • Seu bloqueio / semáforo pode ser compartilhado com threads de outros processos?
  • A sua trava é "reentrada"? - [Normalmente sim].
  • O seu bloqueio está "bloqueando / não bloqueando"? - [Normalmente, o bloqueio não é usado porque bloqueios de bloqueio (também conhecidos como bloqueios de rotação) causam uma espera ocupada].
  • Como você garante que as operações sejam "atômicas"?

Isso depende do seu livro / professor / idioma / biblioteca / ambiente.
Aqui está um tour rápido, observando como alguns idiomas respondem a esses detalhes.


C, C ++ ( pthreads )

  • Um mutex é implementado via pthread_mutex_t. Por padrão, eles não podem ser compartilhados com outros processos ( PTHREAD_PROCESS_PRIVATE), no entanto, os mutex têm um atributo chamado pshared . Quando definido, o mutex é compartilhado entre processos ( PTHREAD_PROCESS_SHARED).
  • Um bloqueio é a mesma coisa que um mutex.
  • Um semáforo é implementado via sem_t. Semelhante aos mutexes, os semáforos podem ser compartilhados entre threasds de muitos processos ou mantidos em sigilo nos threads de um único processo. Isso depende do argumento pshared fornecido para sem_init.

python ( threading.py )

  • Um lock ( threading.RLock) é basicamente o mesmo que C / C ++ pthread_mutex_ts. Ambos são reentrantes . Isso significa que eles só podem ser desbloqueados pelo mesmo encadeamento que o bloqueou. É o caso de sem_tsemáforos, threading.Semaphoresemáforos e theading.Lockbloqueios não são reentrantes - pois é o caso de qualquer encadeamento que pode executar desbloquear a trava / descer o semáforo.
  • Um mutex é igual a um bloqueio (o termo não é usado frequentemente em python).
  • Um semáforo ( threading.Semaphore) é basicamente o mesmo que sem_t. Embora com sem_t, uma fila de IDs de encadeamentos é usada para lembrar a ordem na qual os encadeamentos foram bloqueados ao tentar bloqueá-lo enquanto está bloqueado. Quando um encadeamento desbloqueia um semáforo, o primeiro encadeamento na fila (se houver) é escolhido para ser o novo proprietário. O identificador de segmento é retirado da fila e o semáforo fica bloqueado novamente. No entanto, com threading.Semaphore, um conjunto é usado em vez de uma fila, portanto, a ordem na qual os segmentos foram bloqueados não é armazenada - qualquer segmento no conjunto pode ser escolhido para ser o próximo proprietário.

Java ( java.util.concurrent )

  • Um lock ( java.util.concurrent.ReentrantLock) é basicamente o mesmo que C / C ++ pthread_mutex_te Python, threading.RLockpois também implementa um bloqueio reentrante. Compartilhar bloqueios entre processos é mais difícil em Java por causa da JVM atuando como intermediária. Se um segmento tenta desbloquear um bloqueio que não possui, um IllegalMonitorStateExceptioné acionado.
  • Um mutex é igual a um bloqueio (o termo não é usado frequentemente em Java).
  • Um semáforo ( java.util.concurrent.Semaphore) é basicamente o mesmo que sem_te threading.Semaphore. O construtor para semáforos Java aceita um parâmetro booleano de justiça que controla se deve ser usado um conjunto (falso) ou uma fila (verdadeiro) para armazenar os encadeamentos em espera.

Em teoria, os semáforos são frequentemente discutidos, mas, na prática, os semáforos não são muito usados. Um semáforo contém apenas o estado de um número inteiro; muitas vezes é bastante inflexível e muitos são necessários ao mesmo tempo - causando dificuldade na compreensão do código. Além disso, o fato de qualquer thread poder liberar um semáforo às vezes é indesejável. Primitivas / abstrações de sincronização mais orientadas a objetos / de nível superior, como "variáveis ​​de condição" e "monitores", são usadas.


22

Dê uma olhada no Tutorial Multithreading de John Kopplin.

Na seção Sincronização Entre Encadeamentos , ele explica as diferenças entre evento, bloqueio, mutex, semáforo e timer de espera

Um mutex pode pertencer a apenas um thread por vez, permitindo que os threads coordenem o acesso mutuamente exclusivo a um recurso compartilhado

Os objetos de seção crítica fornecem sincronização semelhante à fornecida por objetos mutex, exceto que os objetos de seção crítica podem ser usados ​​apenas pelos encadeamentos de um único processo

Outra diferença entre um mutex e uma seção crítica é que, se o objeto da seção crítica atualmente pertence a outro encadeamento, EnterCriticalSection()aguarda indefinidamente a propriedade WaitForSingleObject(), enquanto que, usado com um mutex, permite especificar um tempo limite

Um semáforo mantém uma contagem entre zero e algum valor máximo, limitando o número de encadeamentos que acessam simultaneamente um recurso compartilhado.


15

Vou tentar cobri-lo com exemplos:

Bloquear: Um exemplo em que você usaria lockseria um dicionário compartilhado no qual itens (que devem ter chaves exclusivas) são adicionados.
O bloqueio garantiria que um thread não insira o mecanismo de código que está verificando se o item está no dicionário, enquanto outro thread (que está na seção crítica) já passou nessa verificação e está adicionando o item. Se outro encadeamento tentar inserir um código bloqueado, ele aguardará (será bloqueado) até que o objeto seja liberado.

private static readonly Object obj = new Object();

lock (obj) //after object is locked no thread can come in and insert item into dictionary on a different thread right before other thread passed the check...
{
    if (!sharedDict.ContainsKey(key))
    {
        sharedDict.Add(item);
    }
}

Semáforo: Digamos que você tenha um conjunto de conexões; um único encadeamento poderá reservar um elemento no conjunto, aguardando o semáforo obter uma conexão. Em seguida, ele usa a conexão e, quando o trabalho é concluído, libera a conexão liberando o semáforo.

O exemplo de código que eu amo é um dos bouncer fornecidos por @Patric - aqui vai:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace TheNightclub
{
    public class Program
    {
        public static Semaphore Bouncer { get; set; }

        public static void Main(string[] args)
        {
            // Create the semaphore with 3 slots, where 3 are available.
            Bouncer = new Semaphore(3, 3);

            // Open the nightclub.
            OpenNightclub();
        }

        public static void OpenNightclub()
        {
            for (int i = 1; i <= 50; i++)
            {
                // Let each guest enter on an own thread.
                Thread thread = new Thread(new ParameterizedThreadStart(Guest));
                thread.Start(i);
            }
        }

        public static void Guest(object args)
        {
            // Wait to enter the nightclub (a semaphore to be released).
            Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
            Bouncer.WaitOne();          

            // Do some dancing.
            Console.WriteLine("Guest {0} is doing some dancing.", args);
            Thread.Sleep(500);

            // Let one guest out (release one semaphore).
            Console.WriteLine("Guest {0} is leaving the nightclub.", args);
            Bouncer.Release(1);
        }
    }
}

Mutex É bastante Semaphore(1,1)e frequentemente usado globalmente (em todo o aplicativo, caso contrário, locké mais apropriado). Seria usado global Mutexao excluir o nó de uma lista acessível globalmente (última coisa que você deseja que outro encadeamento faça algo enquanto estiver excluindo o nó). Quando você adquire Mutexse um encadeamento diferente tentar adquirir o mesmo, Mutexele será colocado em suspensão até o MESMO encadeamento que o adquiriu Mutex.

Um bom exemplo de criação de mutex global é por @deepee

class SingleGlobalInstance : IDisposable
{
    public bool hasHandle = false;
    Mutex mutex;

    private void InitMutex()
    {
        string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
        string mutexId = string.Format("Global\\{{{0}}}", appGuid);
        mutex = new Mutex(false, mutexId);

        var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
        var securitySettings = new MutexSecurity();
        securitySettings.AddAccessRule(allowEveryoneRule);
        mutex.SetAccessControl(securitySettings);
    }

    public SingleGlobalInstance(int timeOut)
    {
        InitMutex();
        try
        {
            if(timeOut < 0)
                hasHandle = mutex.WaitOne(Timeout.Infinite, false);
            else
                hasHandle = mutex.WaitOne(timeOut, false);

            if (hasHandle == false)
                throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
        }
        catch (AbandonedMutexException)
        {
            hasHandle = true;
        }
    }


    public void Dispose()
    {
        if (mutex != null)
        {
            if (hasHandle)
                mutex.ReleaseMutex();
            mutex.Dispose();
        }
    }
}

então use como:

using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock
{
    //Only 1 of these runs at a time
    GlobalNodeList.Remove(node)
}

Espero que isso poupe algum tempo.


8

A Wikipedia possui uma ótima seção sobre as diferenças entre semáforos e mutexes :

Um mutex é essencialmente a mesma coisa que um semáforo binário e às vezes usa a mesma implementação básica. As diferenças entre eles são:

Os mutexes têm um conceito de proprietário, que é o processo que bloqueou o mutex. Somente o processo que bloqueou o mutex pode desbloqueá-lo. Por outro lado, um semáforo não tem conceito de proprietário. Qualquer processo pode desbloquear um semáforo.

Ao contrário dos semáforos, os mutexes fornecem segurança de inversão prioritária. Como o mutex conhece seu proprietário atual, é possível promover a prioridade do proprietário sempre que uma tarefa de prioridade mais alta começar a aguardar no mutex.

Os mutexes também fornecem segurança para exclusão, onde o processo que contém o mutex não pode ser excluído acidentalmente. Os semáforos não fornecem isso.


5

Meu entendimento é que um mutex é apenas para uso em um único processo, mas em seus diversos threads, enquanto um semáforo pode ser usado em vários processos e nos conjuntos correspondentes de threads.

Além disso, um mutex é binário (bloqueado ou desbloqueado), enquanto um semáforo tem uma noção de contagem ou uma fila de mais de uma solicitação de bloqueio e desbloqueio.

Alguém poderia verificar minha explicação? Estou falando no contexto do Linux, especificamente o Red Hat Enterprise Linux (RHEL) versão 6, que usa o kernel 2.6.32.


3
Agora isso pode ser diferente em diferentes sistemas operacionais, mas em janelas de um Mutex pode ser utilizado por vários processos, pelo menos, o objeto .net Mutex ..
Peter

2
stackoverflow.com/questions/9389730/… "Threads dentro do mesmo processo ou dentro de outros processos podem compartilhar mutexes." portanto, nenhum mutex não deve ser específico do processo.
Peter

3

Usando a programação C em uma variante do Linux como caso base, por exemplo.

Bloqueio:

• Normalmente, um binário de construção muito simples em operação bloqueado ou desbloqueado

• Nenhum conceito de propriedade, prioridade, sequência, etc.

• Geralmente, um bloqueio rotativo em que a linha verifica continuamente a disponibilidade dos bloqueios.

• Geralmente depende de operações atômicas, por exemplo, teste e configuração, comparação e troca, busca e adição, etc.

• Geralmente requer suporte de hardware para operação atômica.

Bloqueios de arquivo:

• Geralmente usado para coordenar o acesso a um arquivo através de vários processos.

• Vários processos podem reter o bloqueio de leitura; no entanto, quando um único processo retém o bloqueio de gravação, nenhum outro processo pode adquirir um bloqueio de leitura ou gravação.

• Exemplo: rebanho, fcntl etc.

Mutex:

• As chamadas de função Mutex geralmente funcionam no espaço do kernel e resultam em chamadas do sistema.

• Utiliza o conceito de propriedade. Somente o segmento que atualmente contém o mutex pode desbloqueá-lo.

• O mutex não é recursivo (exceção: PTHREAD_MUTEX_RECURSIVE).

• Geralmente usado em Association with Condition Variables e passado como argumentos para, por exemplo, pthread_cond_signal, pthread_cond_wait etc.

• Alguns sistemas UNIX permitem que o mutex seja usado por vários processos, embora isso possa não ser imposto em todos os sistemas.

Semáforo:

• Este é um número inteiro mantido pelo kernel cujos valores não podem cair abaixo de zero.

• Pode ser usado para sincronizar processos.

• O valor do semáforo pode ser definido como um valor maior que 1; nesse caso, o valor geralmente indica o número de recursos disponíveis.

• Um semáforo cujo valor é restrito a 1 e 0 é chamado de semáforo binário.


0

Supporting ownership, maximum number of processes share lockE o maximum number of allowed processes/threads in critical sectionsão três os principais factores que determinam o nome / tipo do objecto simultâneo com o nome geral de lock. Como o valor desses fatores é binário (tem dois estados), podemos resumi-los em uma tabela semelhante a verdade 3 * 8.

  • X (Suporta propriedade?): Não (0) / sim (1)
  • Y (# processos de compartilhamento):> 1 (∞) / 1
  • Z (# processos / threads na CA):> 1 (∞) / 1

  X   Y   Z          Name
 --- --- --- ------------------------
  0   ∞   ∞   Semaphore              
  0   ∞   1   Binary Semaphore       
  0   1   ∞   SemaphoreSlim          
  0   1   1   Binary SemaphoreSlim(?)
  1   ∞   ∞   Recursive-Mutex(?)     
  1   ∞   1   Mutex                  
  1   1   ∞   N/A(?)                 
  1   1   1   Lock/Monitor           

Sinta-se livre para editar ou expandir esta tabela, eu a publiquei como uma tabela ascii para ser editável :)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.