ti-enxame.com

Qual é o bloqueio mutex básico mais eficiente ou número inteiro atômico?

Para algo simples como um contador, se vários segmentos aumentarem o número. Eu li que os bloqueios mutex podem diminuir a eficiência, pois os threads precisam esperar. Então, para mim, um contador atômico seria o mais eficiente, mas eu li que internamente é basicamente um cadeado? Então, acho que estou confuso sobre como um pode ser mais eficiente que o outro.

46
Matt

Se você tiver um contador para o qual operações atômicas são suportadas, será mais eficiente que um mutex.

Tecnicamente, o atômico bloqueará o barramento de memória na maioria das plataformas. No entanto, existem dois detalhes melhoradores:

  • É impossível suspender um encadeamento durante o bloqueio do barramento de memória, mas é possível suspender um encadeamento durante um bloqueio mutex. É isso que permite obter uma garantia livre de bloqueio (que não diz nada sobre não bloquear - apenas garante que pelo menos um segmento avance).
  • Os mutexes acabam sendo implementados com átomos. Como você precisa de pelo menos uma operação atômica para bloquear um mutex e uma operação atômica para desbloquear um mutex, leva pelo menos duas vezes o tempo para fazer um bloqueio mutex, mesmo nos melhores casos.
25
Cort Ammon

As operações atômicas aproveitam o suporte do processador (instruções de comparação e troca) e não usam bloqueios, enquanto os bloqueios dependem mais do sistema operacional e têm desempenho diferente, por exemplo, no Win e no Linux.

Na verdade, os bloqueios suspendem a execução do encadeamento, liberando recursos da CPU para outras tarefas, mas incorrendo em sobrecarga óbvia de alternância de contexto ao parar/reiniciar o encadeamento. Pelo contrário, os encadeamentos que tentam operações atômicas não esperam e continuam tentando até o sucesso (chamados de espera ocupada), para que não ocorram na sobrecarga de alternância de contexto, mas também não liberam recursos da CPU.

Resumindo, em geral as operações atômicas são mais rápidas se a contenção entre os encadeamentos for suficientemente baixa. Você definitivamente deve fazer testes comparativos, pois não há outro método confiável de saber qual é a menor sobrecarga entre alternância de contexto e espera ocupada.

23
yahe

Uma implementação mutex mínima (compatível com os padrões) requer 2 ingredientes básicos:

  • Uma maneira de transmitir atomicamente uma alteração de estado entre threads (o estado 'bloqueado')
  • barreiras de memória para impor operações de memória protegidas pelo mutex para permanecer dentro da área protegida.

Não há como você tornar isso mais simples do que isso, devido ao relacionamento 'sincroniza com' exigido pelo padrão C++.

Uma implementação mínima (correta) pode ser assim:

class mutex {
    std::atomic<bool> flag{false};

public:
    void lock()
    {
        while (flag.exchange(true, std::memory_order_relaxed));
        std::atomic_thread_fence(std::memory_order_acquire);
    }

    void unlock()
    {
        std::atomic_thread_fence(std::memory_order_release);
        flag.store(false, std::memory_order_relaxed);
    }
};

Devido à sua simplicidade (não pode suspender o encadeamento de execução), é provável que, sob baixa contenção, essa implementação supere um std::mutex. Mas, mesmo assim, é fácil ver que cada incremento inteiro, protegido por esse mutex, requer as seguintes operações:

  • uma loja atomic para liberar o mutex
  • um atomic compare-and-swap (read-modify-write) para adquirir o mutex (possivelmente várias vezes)
  • um incremento inteiro

Se você comparar isso com um std::atomic<int> Independente, que é incrementado com uma única leitura (modificar) incondicionalmente (por exemplo, fetch_add), É razoável esperar que uma operação atômica (usando o mesmo modelo de pedido) superará o caso em que um mutex é usado.

9
LWimsey

As classes de variável atômica em Java são capazes de tirar proveito das instruções de comparação e troca fornecidas pelo processador.

Aqui está uma descrição detalhada das diferenças: http://www.ibm.com/developerworks/library/j-jtp11234/

6
Ajay

inteiro atômico é um objeto modo de usuário lá, pois é muito mais eficiente que um mutex que roda em modo de kernel. O escopo do número inteiro atômico é um aplicativo único, enquanto o escopo do mutex é para todos os softwares em execução na máquina.

4
RonTLV

Mutex é uma semântica no nível do kernel que fornece exclusão mútua mesmo no Process level. Observe que pode ser útil para estender a exclusão mútua entre os limites do processo e não apenas dentro de um processo (para threads). É mais caro.

O contador atômico, AtomicInteger, por exemplo, é baseado no CAS e geralmente tenta fazer a operação até obter êxito. Basicamente, nesse caso, os threads disparam ou competem para aumentar\decrementar o valor atomicamente. Aqui, você pode ver bons ciclos de CPU sendo usados ​​por um encadeamento tentando operar com um valor atual.

Como você deseja manter o contador, AtomicInteger\AtomicLong será o melhor para o seu caso de uso.

1
Sunil Singhal

A maioria dos processadores suporta uma leitura ou gravação atômica, e frequentemente um cmp & swap atômico. Isso significa que o próprio processador grava ou lê o valor mais recente em uma única operação e pode haver alguns ciclos perdidos em comparação com um acesso inteiro normal, especialmente porque o compilador não pode otimizar as operações atômicas tão bem quanto o normal.

Por outro lado, um mutex é um número de linhas de código para entrar e sair, e durante essa execução outros processadores que acessam o mesmo local ficam totalmente paralisados, o que claramente representa uma grande sobrecarga para eles. No código de alto nível não otimizado, o mutex enter/exit e o atômico serão chamadas de função, mas, para o mutex, qualquer processador concorrente será bloqueado enquanto a função de mutex enter retorna e enquanto a função de saída é iniciada. Para atômica, é apenas a duração da operação real que está bloqueada. A otimização deve reduzir esse custo, mas não todo.

Se você está tentando incrementar, então seu processador moderno provavelmente suporta incremento/decremento atômico, o que será ótimo.

Caso contrário, ele é implementado usando o processador atômico cmp & swap ou usando um mutex.

Mutex:

get the lock
read
increment
write
release the lock

Cmp atômico e troca:

atomic read the value
calc the increment
do{
   atomic cmpswap value, increment
   recalc the increment
}while the cmp&swap did not see the expected value

Portanto, esta segunda versão possui um loop [caso outro processador aumente o valor entre nossas operações atômicas, para que o valor não corresponda mais e o incremento esteja errado] que pode demorar muito tempo [se houver muitos concorrentes], mas geralmente ainda deve ser mais rápido do que a versão mutex, mas a versão mutex pode permitir que esse processador alterne entre tarefas.

1
Gem Taylor