c++ - pthread_create - ¿Sobrecarga de mutexes pthread?
pthreads php (9)
"Un mutex requiere un cambio de contexto del sistema operativo. Eso es bastante caro".
- Esto no es cierto en Linux, donde los mutexes se implementan usando algo llamado futex''es. Adquirir un mutex no impugnado (es decir, no ya bloqueado) es, como señala cmeerw, una cuestión de unas pocas instrucciones simples, y típicamente está en el área de 25 nanosegundos con hardware actual.
Para más información: Futex
Estoy tratando de hacer una API de C ++ (para Linux y Solaris) segura para subprocesos, para que sus funciones puedan ser llamadas desde diferentes subprocesos sin romper las estructuras internas de datos. En mi enfoque actual, estoy usando pthread mutexes para proteger todos los accesos a las variables miembro. Esto significa que una función getter simple ahora bloquea y desbloquea un mutex, y me preocupan los gastos generales de esto, especialmente porque la API se usará principalmente en aplicaciones de subproceso único donde cualquier bloqueo mutex parece una sobrecarga pura.
Entonces, me gustaría preguntar:
- ¿tiene alguna experiencia con el rendimiento de las aplicaciones de un único subproceso que utilizan bloqueo frente a las que no?
- cuán caras son estas llamadas de bloqueo / desbloqueo, en comparación con, por ejemplo, un simple acceso "return this-> isActive" para una variable miembro bool?
- ¿conoces mejores formas de proteger esos accesos variables?
Bien, un enfoque subóptimo pero simple es colocar macros alrededor de los bloqueos de su mutex y desbloquea. Luego, tenga una opción de compilación / archivo MAKE para habilitar / deshabilitar el enhebrado.
Ex.
#ifdef THREAD_ENABLED
#define pthread_mutex_lock(x) ... //actual mutex call
#endif
#ifndef THREAD_ENABLED
#define pthread_mutex_lock(x) ... //do nothing
#endif
Luego, al compilar, haga un gcc -DTHREAD_ENABLED
para habilitar el enhebrado.
De nuevo, NO usaría este método en ningún proyecto grande. Pero solo si quieres algo bastante simple.
Esto es un poco fuera de tema, pero pareces ser nuevo en el uso de hilos, por un lado, solo bloquea donde los hilos pueden superponerse. Luego, intenta minimizar esos lugares. Además, en lugar de tratar de bloquear todos los métodos, piense en qué está haciendo el hilo (en general) con un objeto y realice una sola llamada, y bloquee eso. Intente colocar los bloqueos lo más arriba posible (esto nuevamente aumenta la eficiencia y puede / ayuda / para evitar el bloqueo). Pero las cerraduras no se "componen", tiene que al menos mentalmente organizar transversalmente su código por donde se encuentran los hilos y se superponen.
Hice una biblioteca similar y no tuve ningún problema con el rendimiento de bloqueo. (No puedo decirle exactamente cómo se implementan, por lo que no puedo decir de manera concluyente que no es gran cosa).
Preferiría hacerlo bien primero (es decir, usar bloqueos) y luego preocuparme por el rendimiento. No sé de una mejor manera; para eso fueron construidos los mutexes.
Una alternativa para los clientes de subproceso único sería utilizar el preprocesador para compilar una versión no bloqueada o bloqueada de su biblioteca. P.ej:
#ifdef BUILD_SINGLE_THREAD
inline void lock () {}
inline void unlock () {}
#else
inline void lock () { doSomethingReal(); }
inline void unlock () { doSomethingElseReal(); }
#endif
Por supuesto, eso agrega una compilación adicional para mantener, ya que distribuiría versiones de uno o varios hilos.
Para el acceso variable miembro, debe usar bloqueos de lectura / escritura, que tienen una sobrecarga ligeramente menor y permiten múltiples lecturas concurrentes sin bloqueo.
En muchos casos puede usar construcciones internas atómicas, si su compilador las proporciona (si está utilizando gcc o icc __sync_fetch * () y similares), pero no son muy difíciles de manejar correctamente.
Si puede garantizar que el acceso sea atómico (por ejemplo, en x86 una lectura o escritura dword es siempre atómica, si está alineada, pero no es una lectura-modificación-escritura), a menudo puede evitar bloqueos y usar en su lugar volátiles, pero esto no es portátil y requiere conocimiento del hardware.
Puedo decirle desde Windows, que un mutex es un objeto kernel y, como tal, incurre en una sobrecarga de bloqueo (relativamente) significativa. Para obtener un bloqueo de mejor rendimiento, cuando todo lo que necesita es uno que funcione en subprocesos, es utilizar una sección crítica. Esto no funcionaría en los procesos, solo los hilos en un solo proceso.
Sin embargo ... linux es una bestia bastante diferente al bloqueo multiproceso. Sé que un mutex se implementa usando las instrucciones de la CPU atómica y solo se aplica a un proceso, por lo que tendrían el mismo rendimiento que una sección crítica de win32, es decir, sería muy rápido.
Por supuesto, el bloqueo más rápido es no tener ninguno, o usarlos lo menos posible (pero si su lib se va a usar en un entorno con muchos hilos, querrá bloquearlo por el menor tiempo posible): bloquear, hacer algo, desbloquear, hacer otra cosa, luego bloquear nuevamente es mejor que mantener el candado en toda la tarea; el costo de bloqueo no está en el tiempo necesario para bloquearlo, sino en el momento en que un hilo se mueve dando vueltas esperando para otro hilo para liberar un bloqueo que quiere!)
Tenía curiosidad sobre el gasto de usar un pthred_mutex_lock/unlock
. Tuve un escenario en el que necesitaba copiar entre 1500-65K bytes sin usar un mutex o usar un mutex y hacer una sola escritura de un puntero a los datos necesarios.
Escribí un pequeño ciclo para probar cada
gettimeofday(&starttime, NULL)
COPY DATA
gettimeofday(&endtime, NULL)
timersub(&endtime, &starttime, &timediff)
print out timediff data
o
ettimeofday(&starttime, NULL)
pthread_mutex_lock(&mutex);
gettimeofday(&endtime, NULL)
pthread_mutex_unlock(&mutex);
timersub(&endtime, &starttime, &timediff)
print out timediff data
Si estaba copiando menos de 4000 o más bytes, entonces la operación de copia directa tomó menos tiempo. Sin embargo, si estaba copiando más de 4000 bytes, entonces era menos costoso hacer el bloqueo / desbloqueo mutex.
La sincronización en el bloqueo / desbloqueo de exclusión mutua funcionó entre 3 y 5 veces el uso, incluido el tiempo de gettimeofday para el tiempo actual, que tomó aproximadamente 2 usos.
Todas las implementaciones de subprocesos modernos pueden manejar un bloqueo de exclusión mutua no previsto por completo en el espacio de usuario (con solo un par de instrucciones de la máquina); solo cuando hay una disputa, la biblioteca debe llamar al kernel.
Otro punto a considerar es que si una aplicación no se vincula explícitamente a la biblioteca pthread (porque es una aplicación de subproceso único), solo obtendrá funciones pthread ficticias (que no hacen ningún bloqueo en absoluto), solo si el la aplicación es multihebra (y enlaces a la biblioteca pthread), se usarán las funciones pthread completas.
Y finalmente, como ya han señalado otros, no tiene sentido proteger un método getter para algo como isActive con un mutex: una vez que la persona que llama tiene la oportunidad de ver el valor devuelto, el valor puede haber sido cambiado (como el mutex solo está bloqueado dentro del método getter).
Un mutex requiere un cambio de contexto de sistema operativo. Eso es bastante caro. La CPU aún puede hacerlo cientos de miles de veces por segundo sin demasiados problemas, pero es mucho más costoso que no tener el mutex allí. Ponerlo en cada acceso variable es probablemente excesivo.
Probablemente tampoco sea lo que quieres. Este tipo de bloqueo de fuerza bruta tiende a provocar bloqueos.
¿conoces mejores formas de proteger esos accesos variables?
Diseña tu aplicación para compartir la menor cantidad de datos posible. Algunas secciones de código deben estar sincronizadas, probablemente con un mutex, pero solo aquellas que son realmente necesarias. Y, por lo general, no acceso a variables individuales , sino tareas que contienen grupos de accesos variables que deben realizarse atómicamente. (Quizás necesite establecer su indicador is_active
junto con algunas otras modificaciones. ¿Tiene sentido establecer esa bandera y no hacer más cambios al objeto?)