Eu tenho uma lista que eu quero tópicos diferentes para pegar elementos. Para evitar o bloqueio do mutex que guarda a lista quando está vazio, eu verifico empty()
antes de bloquear.
Tudo bem se a chamada para list::empty()
não estiver correta 100% do tempo. Eu só quero evitar bater ou interromper chamadas list::push()
e concorrentes list::pop()
.
Estou seguro de presumir que o VC ++ e o Gnu GCC às vezes vão empty()
errar e nada pior?
if(list.empty() == false){ // unprotected by mutex, okay if incorrect sometimes
mutex.lock();
if(list.empty() == false){ // check again while locked to be certain
element = list.back();
list.pop_back();
}
mutex.unlock();
}
c++
multithreading
optimization
race-condition
stdlist
Anne Quinn
fonte
fonte
std::list::size
garantiu uma complexidade de tempo constante, o que basicamente significa que o tamanho (número de nós) precisa ser armazenado em uma variável separada; vamos chamá-losize_
.std::list::empty
provavelmente retorna algo comosize_ == 0
, e a leitura e gravação simultâneassize_
causariam corrida de dados, portanto, UB.Respostas:
Não, não está bem. Se você verificar se a lista está vazia fora de algum mecanismo de sincronização (bloqueando o mutex), você terá uma corrida de dados. Ter uma corrida de dados significa que você tem um comportamento indefinido. Ter um comportamento indefinido significa que não podemos mais raciocinar sobre o programa e qualquer resultado obtido é "correto".
Se você valoriza sua sanidade, você pega o desempenho e bloqueia o mutex antes de verificar. Dito isto, a lista pode até não ser o contêiner correto para você. Se você puder nos informar exatamente o que está fazendo, sugerimos um contêiner melhor.
fonte
list::empty()
é uma ação de leitura que não tem nada a ver comrace-condition
read-write
ouwrite-write
. Se você não me acreditar, dar a seção padrão em corridas de dados uma leituraHá uma leitura e uma gravação (provavelmente para o
size
membro destd::list
, se assumirmos que ele tem esse nome) que não são sincronizadas no reagard entre si . Imagine que um thread chameempty()
(na sua parte externaif()
) enquanto o outro thread entra no interiorif()
e executapop_back()
. Você está lendo uma variável que está possivelmente sendo modificada. Esse é um comportamento indefinido.fonte
Como um exemplo de como as coisas podem dar errado:
Um compilador suficientemente inteligente pode ver que
mutex.lock()
não é possível alterar olist.empty()
valor de retorno e, assim, pularif
completamente a verificação interna , levando apop_back
uma lista que teve seu último elemento removido após o primeiroif
.Por que isso pode fazer isso? Não há sincronização
list.empty()
, portanto, se ela fosse alterada simultaneamente, isso constituiria uma corrida de dados. O padrão diz que os programas não devem ter corridas de dados; portanto, o compilador tomará isso como garantido (caso contrário, ele poderia executar quase nenhuma otimização). Portanto, ele pode assumir uma perspectiva de thread único no não sincronizadolist.empty()
e concluir que ele deve permanecer constante.Essa é apenas uma das várias otimizações (ou comportamentos de hardware) que podem quebrar seu código.
fonte
a.load()+a.load()
...a.load()*2
é óbvia? Nem mesmoa.load(rel)+b.load(rel)-a.load(rel)
é otimizado de qualquer maneira. Nada é. Por que você espera que os bloqueios (que por essência têm consistência seq) sejam mais otimizados?a.load() + a.load()
a2 * a.load()
. Sinta-se à vontade para fazer uma pergunta sobre isso, se quiser saber mais.