std :: lock_guard ou std :: scoped_lock?

143

O C ++ 17 introduziu uma nova classe de bloqueio chamada std::scoped_lock.

A julgar pela documentação, ela se parece com a std::lock_guardclasse já existente .

Qual é a diferença e quando devo usá-la?

Stephan Dollberg
fonte

Respostas:

125

A scoped_locké uma versão estritamente superior lock_guardque bloqueia um número arbitrário de semáforos todos de uma só vez (com o mesmo algoritmo para evitar impasse como std::lock). No novo código, você deve sempre usar scoped_lock.

O único motivo que lock_guardainda existe é a compatibilidade. Não pôde ser excluído apenas porque é usado no código atual. Além disso, mostrou-se indesejável alterar sua definição (de unária para variável), porque essa também é uma mudança observável e, portanto, quebradora (mas por razões um tanto técnicas).

Kerrek SB
fonte
8
Além disso, graças à dedução do argumento do modelo de classe, você nem precisa listar os tipos bloqueáveis.
Nicol Bolas 25/03
3
@ NicolBolas: Isso é verdade, mas isso também se aplica lock_guard. Mas certamente torna as classes de guarda um pouco mais fáceis de usar.
27417 Kerrek SB
6
scoped_lock é somente em C ++ 17 #
Shital Shah
1
Como é o c ++ 17, a compatibilidade é um motivo particularmente bom para sua existência. Também discordo veementemente de qualquer afirmação absolutista de "você só deve usar" quando a tinta ainda está secando desse padrão.
Paul Childs
88

A diferença única e importante é que std::scoped_lockum construtor variadico recebe mais de um mutex. Isso permite bloquear vários mutexes em um impasse, evitando como se std::lockfossem usados.

{
    // safely locked as if using std::lock
    std::scoped_lock<std::mutex, std::mutex> lock(mutex1, mutex2);     
}

Anteriormente, você tinha que dançar um pouco para bloquear vários mutexes de uma maneira segura, usando a std::lockexplicação desta resposta .

A adição do bloqueio de escopo facilita o uso e evita os erros relacionados. Você pode considerar std::lock_guardobsoleto. O caso de argumento único de std::scoped_lockpode ser implementado como uma especialização e você não precisa se preocupar com possíveis problemas de desempenho.

O GCC 7 já tem suporte para o std::scoped_lockqual pode ser visto aqui .

Para obter mais informações, leia o documento padrão.

Stephan Dollberg
fonte
9
Respondeu sua própria pergunta após apenas 10 minutos. Você realmente não sabia?
285 Walter Walter
23
@Walter eu fiz stackoverflow.blog/2011/07/01/…
Stephan Dollberg
3
Quando o levei à comissão, a resposta foi "nada". Pode ser que, no caso degenerado de algum algoritmo, essa seja exatamente a coisa certa. Ou pode ser que muitas pessoas acidentalmente bloqueiem nada quando pretendem bloquear algo é um problema comum. Eu realmente não tenho certeza.
Howard Hinnant
3
@HowardHinnant: scoped_lock lk; // locks all mutexes in scope. LGTM.
27417 Kerrek SB
2
@KerrekSB: scoped_lock lk;é o novo atalho para scoped_lock<> lk;. Não são há semáforos. Então você está certo. ;-)
Howard Hinnant 25/03
26

Resposta tardia e principalmente em resposta a:

Você pode considerar std::lock_guardobsoleto.

No caso comum de que é necessário bloquear exatamente um mutex, std::lock_guardexiste uma API um pouco mais segura de usar do que scoped_lock.

Por exemplo:

{
   std::scoped_lock lock; // protect this block
   ...
}

O trecho de código acima provavelmente é um erro acidental em tempo de execução porque ele compila e, em seguida, não faz absolutamente nada. O codificador provavelmente significava:

{
   std::scoped_lock lock{mut}; // protect this block
   ...
}

Agora ele bloqueia / desbloqueia mut.

Se lock_guardfoi usado nos dois exemplos acima, o primeiro exemplo é um erro em tempo de compilação em vez de um erro em tempo de execução, e o segundo exemplo tem funcionalidade idêntica à versão usada scoped_lock.

Portanto, meu conselho é usar a ferramenta mais simples para o trabalho:

  1. lock_guard se você precisar bloquear exatamente 1 mutex para um escopo inteiro.

  2. scoped_lock se você precisar bloquear um número de mutexes que não sejam exatamente 1.

  3. unique_lockse você precisar desbloquear dentro do escopo do bloco (que inclui o uso de a condition_variable).

Este conselho é que não implica que scoped_lockdeve ser redesenhado para não aceitar 0 semáforos. Existem casos de uso válidos nos quais é desejável scoped_lockaceitar pacotes de parâmetros de modelos variados que possam estar vazios. E a caixa vazia não deve bloquear nada.

E é por isso que lock_guardnão é preterido. scoped_lock e unique_lock pode ser um superconjunto de funcionalidades de lock_guard, mas esse fato é uma faca de dois gumes. Às vezes, é tão importante o que um tipo não fará (construção padrão neste caso).

Howard Hinnant
fonte
13

Aqui está um exemplo e uma citação de C ++ Concurrency in Action :

friend void swap(X& lhs, X& rhs)
{
    if (&lhs == & rhs)
        return;
    std::lock(lhs.m, rhs.m);
    std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
    std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
    swap(lhs.some_detail, rhs.some_detail);
}

vs.

friend void swap(X& lhs, X& rhs)
{
    if (&lhs == &rhs)
        return;
    std::scoped_lock guard(lhs.m, rhs.m);
    swap(lhs.some_detail, rhs.some_detail);
}

A existência de std::scoped_locksignifica que a maioria dos casos em que você usaria std::lockantes do c ++ 17 agora pode ser escrita usando std::scoped_lock, com menos potencial para erros, o que só pode ser uma coisa boa!

陳 力
fonte