empezar con una lista enlazada vacío de subprocesos en espera. El cabezal debe establecerse en 0.
Use CAS, compare y cambie, para insertar un hilo al principio de la lista de camareros. Si el encabezado = -1, no inserte ni espere. Puede usar CAS de manera segura para insertar elementos al principio de una lista vinculada si lo hace bien.
Después de insertarse, el hilo de espera debe esperar en SIGUSR1. Use sigwait() para hacer esto.
Cuando esté listo, el hilo de señalización usa CAS para establecer el jefe de la lista de espera en -1. Esto evita que se agreguen más subprocesos a la lista de espera. Luego, el hilo de señalización itera los hilos en la lista de espera y llama a pthread_kill (& hilo, SIGUSR1) para activar cada hilo en espera.
Si se envía SIGUSR1 antes de una llamada a sigwait, sigwait regresará inmediatamente. Por lo tanto, no habrá una carrera entre agregar un hilo a la lista de espera y llamar a sigwait.
EDIT:
CAS ¿Por qué es más rápido que un mutex? La respuesta de Laymen (soy un hombre común). Es más rápido para algunas cosas en algunas situaciones, ya que tiene una sobrecarga menor cuando NO hay raza. Entonces, si puede reducir su problema concurrente a la necesidad de cambiar 8-16-32-64-128 bits de memoria contigua, y una carrera no va a suceder muy a menudo, CAS gana. CAS es básicamente una instrucción mov un poco más sofisticada/costosa, donde ibas a hacer un "mov" regular de todos modos. Es una "exclusión de bloqueo" o algo así.
Un mutex por otro lado es un montón de cosas extra, que ensucia otras líneas de caché y usa más barreras de memoria, etc. Aunque CAS actúa como una barrera de memoria en el x86, x64, etc. Entonces, por supuesto tienes que desbloquear el mutex, que es probablemente la misma cantidad de material extra.
Aquí es cómo agregar un elemento a una lista enlazada usando CAS:
while (1)
{
pOldHead = pHead; <-- snapshot of the world. Start of the race.
pItem->pNext = pHead;
if (CAS(&pHead, pOldHead, pItem)) <-- end of the race if phead still is pOldHead
break; // success
}
Entonces, ¿Con qué frecuencia piensa su código va a tener múltiples hilos en esa línea CAS exactamente al mismo tiempo? En realidad ... no muy a menudo. Hicimos pruebas que acababan de realizar bucles para agregar millones de elementos con múltiples hilos al mismo tiempo, y eso ocurre mucho menos del 1% del tiempo. En un programa real, puede que nunca suceda.
Obviamente si hay una carrera, tienes que volver atrás y hacer ese ciclo otra vez, pero en el caso de una lista vinculada, ¿qué te cuesta eso?
La desventaja es que no puede hacer cosas muy complejas en esa lista vinculada si va a utilizar ese método para agregar elementos al encabezado. Intente implementar una lista de doble enlace. Que dolor.
EDIT:
En el código anterior utilizo una macro CAS. Si está usando linux, CAS = macro usando __sync_bool_compare_and_swap. Ver gcc atomic builtins. Si está usando Windows, CAS = macro usando algo como InterlockedCompareExchange. Esto es lo que una función en línea en las ventanas podría ser:
inline bool CAS(volatile WORD* p, const WORD nOld, const WORD nNew) {
return InterlockedCompareExchange16((short*)p, nNew, nOld) == nOld;
}
inline bool CAS(volatile DWORD* p, const DWORD nOld, const DWORD nNew) {
return InterlockedCompareExchange((long*)p, nNew, nOld) == nOld;
}
inline bool CAS(volatile QWORD* p, const QWORD nOld, const QWORD nNew) {
return InterlockedCompareExchange64((LONGLONG*)p, nNew, nOld) == nOld;
}
inline bool CAS(void*volatile* p, const void* pOld, const void* pNew) {
return InterlockedCompareExchangePointer(p, (PVOID)pNew, (PVOID)pOld) == pOld;
}
Su tiempo (! GlobalFlag) es el equivalente de un bloqueo de bucle. Pero me temo que no entiendo la pregunta. Si usa una señal, lo que haría, entonces todavía usa una forma de semáforo o mutex (aunque esté oculto por el sistema). Sin esperar una señal, tendría un spinlock. Usar globalflag es bueno si puede trabajar mientras espera y cambiar a una tarea diferente (o tarea adicional) una vez que la bandera es verdadera. –
Además de lo que tiene aquí, probablemente tenga que declarar globalflag como volátil para asegurarse de que el compilador realmente lea la memoria en lugar de simplemente mirar el código y decidir que no puede cambiar el valor. Y use algún tipo de barrera de memoria para garantizar que los valores escritos por un hilo sean visibles en otro, – jcoder