Tengo una lista de la que quiero diferentes hilos para tomar elementos. Para evitar bloquear el mutex que protege la lista cuando está vacío, verifico empty()
antes de bloquear.
Está bien si la llamada a list::empty()
no es correcta el 100% del tiempo. Solo quiero evitar fallar o interrumpir concurrentes list::push()
y list::pop()
llamadas.
¿Estoy seguro de asumir que VC ++ y Gnu GCC solo a veces se empty()
equivocan y nada peor?
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
fuente
fuente
std::list::size
ha garantizado una complejidad de tiempo constante, lo que básicamente significa que el tamaño (número de nodos) debe almacenarse en una variable separada; Digamos que essize_
.std::list::empty
entonces es probable que devuelva algo comosize_ == 0
, y la lectura y escritura concurrentessize_
causarían una carrera de datos, por lo tanto, UB.Respuestas:
No, no esta bien. Si verifica si la lista está vacía fuera de algún mecanismo de sincronización (bloqueando el mutex), entonces tiene una carrera de datos. Tener una carrera de datos significa que tienes un comportamiento indefinido. Tener un comportamiento indefinido significa que ya no podemos razonar sobre el programa y cualquier resultado que obtenga es "correcto".
Si valora su cordura, tomará el golpe de rendimiento y bloqueará el mutex antes de verificar. Dicho esto, es posible que la lista ni siquiera sea el contenedor correcto para usted. Si puede hacernos saber exactamente qué está haciendo con él, podríamos sugerirle un mejor contenedor.
fuente
list::empty()
es una acción de lectura que no tiene nada que ver conrace-condition
read-write
owrite-write
. Si no meHay una lectura y una escritura (muy probablemente para el
size
miembro destd::list
, si suponemos que se llama así) que no están sincronizadas entre sí . Imagina que un hilo llamaempty()
(en tu exteriorif()
) mientras que el otro hilo entra en el interiorif()
y se ejecutapop_back()
. Entonces está leyendo una variable que posiblemente se está modificando. Este es un comportamiento indefinido.fuente
Como ejemplo de cómo las cosas podrían salir mal:
Un compilador suficientemente inteligente podría ver que
mutex.lock()
no es posible cambiar ellist.empty()
valor de retorno y, por lo tanto, omitir laif
comprobación interna por completo, lo que finalmente lleva apop_back
una lista que eliminó su último elemento después del primeroif
.¿Por qué puede hacer eso? No hay sincronización
list.empty()
, por lo tanto, si se cambiara simultáneamente, constituiría una carrera de datos. El estándar dice que los programas no tendrán carreras de datos, por lo que el compilador dará eso por sentado (de lo contrario, casi no podría realizar ninguna optimización). Por lo tanto, puede asumir una perspectiva de subproceso único en lo no sincronizadolist.empty()
y concluir que debe permanecer constante.Esta es solo una de varias optimizaciones (o comportamientos de hardware) que podrían romper su código.
fuente
a.load()+a.load()
...a.load()*2
es obvia? Ni siquieraa.load(rel)+b.load(rel)-a.load(rel)
está optimizado de todos modos. Nada es. ¿Por qué esperas que las cerraduras (que en su mayoría tienen una consistencia seq) sean más optimizadas?a.load() + a.load()
a2 * a.load()
. Siéntase libre de hacer una pregunta sobre esto si desea saber más.