c++ multithreading optimization race-condition stdlist

c++ - list:: empty() comportamiento de subprocesos mĂșltiples?



multithreading optimization (3)

Está bien si la llamada a la list::empty() no es correcta el 100% del tiempo.

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.

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 la list::empty() no es correcta el 100% del tiempo. Solo quiero evitar fallar o interrumpir las llamadas concurrentes list::push() y list::pop() .

¿Estoy seguro de suponer que VC ++ y Gnu GCC solo a veces se empty() mal 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(); }


Como ejemplo de cómo las cosas podrían salir mal:

Un compilador lo suficientemente inteligente podría ver que mutex.lock() no puede cambiar el valor de retorno list.empty() y, por lo tanto, omitir por completo la comprobación if interna, lo que finalmente lleva a un pop_back en una lista que eliminó su último elemento después del primer if .

¿Por qué puede hacer eso? No hay sincronización en 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 lo dará por sentado (de lo contrario, casi no podría realizar ninguna optimización). Por lo tanto, puede asumir una perspectiva de subproceso único en la lista no sincronizada.empty list.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.


Hay una lectura y una escritura (muy probablemente para el miembro de size de std::list , si suponemos que se llama así) que no están sincronizadas entre sí . Imagine que un hilo llama a empty() (en su if() externo) mientras que el otro hilo ingresa al if() interno y ejecuta pop_back() . Entonces está leyendo una variable que posiblemente se está modificando. Este es un comportamiento indefinido.