synchronization - ¿Cuándo se debe usar un spinlock en lugar de mutex?
(6)
El uso de spinlocks en un sistema de un solo núcleo / una sola CPU no tiene sentido, ya que mientras el sondeo de spinlock esté bloqueando el único núcleo de la CPU disponible, no se puede ejecutar ningún otro subproceso y ningún otro subproceso puede ejecutarse, el bloqueo no funcionará. ser desbloqueado tampoco. IOW, un spinlock solo pierde tiempo de CPU en esos sistemas sin ningún beneficio real
Esto está mal. No hay desperdicio de ciclos de CPU en el uso de los cierres giratorios en los sistemas con procesador uni, porque una vez que el proceso se bloquea, la preferencia se desactiva, por lo que no puede haber nadie más girando. ¡Es solo que usarlo no tiene ningún sentido! Por lo tanto, los spinlocks en los sistemas Uni son reemplazados por preempt_disable en tiempo de compilación por el kernel.
Creo que ambos están haciendo el mismo trabajo, ¿cómo decides cuál usar para la sincronización?
La respuesta de Mecki lo clava bastante bien. Sin embargo, en un solo procesador, el uso de un spinlock podría tener sentido cuando la tarea está esperando que el bloqueo sea interrumpido por una rutina de servicio de interrupción. La interrupción transferiría el control al ISR, que prepararía el recurso para que lo utilice la tarea en espera. Terminaría liberando el bloqueo antes de devolver el control a la tarea interrumpida. La tarea de hilado encontraría el spinlock disponible y continuaría.
Los mecanismos de sincronización de Spinlock y Mutex son muy comunes hoy en día.
Pensemos en Spinlock primero.
Básicamente, es una acción de espera ocupada, lo que significa que debemos esperar a que se libere un bloqueo específico antes de poder continuar con la siguiente acción. Conceptualmente muy simple, mientras que su implementación no está en el caso. Por ejemplo: si el bloqueo no se ha liberado, entonces el hilo se cambió y entró en el estado de suspensión, ¿deberíamos resolverlo? ¿Cómo lidiar con los bloqueos de sincronización cuando dos hilos solicitan acceso simultáneamente?
En general, la idea más intuitiva es lidiar con la sincronización a través de una variable para proteger la sección crítica. El concepto de Mutex es similar, pero siguen siendo diferentes. Centrarse en: la utilización de la CPU. Spinlock consume tiempo de CPU para esperar a realizar la acción y, por lo tanto, podemos resumir la diferencia entre los dos:
En entornos homogéneos de múltiples núcleos, si el tiempo que se dedica a la sección crítica es muy pequeño, utilice Spinlock, ya que podemos reducir el tiempo de cambio de contexto. (La comparación de un solo núcleo no es importante, ya que algunos sistemas implementan Spinlock en el centro del conmutador)
En Windows, usar Spinlock actualizará el hilo a DISPATCH_LEVEL, que en algunos casos puede no estar permitido, por lo que esta vez tuvimos que usar un Mutex (APC_LEVEL).
Tenga en cuenta también que en ciertos entornos y condiciones (como la ejecución en ventanas en el nivel de envío> = NIVEL DE DESPACHO), no puede usar la exclusión mutua sino el bloqueo de giro. En Unix - Lo mismo.
Aquí hay una pregunta equivalente en el sitio de unix de stackexchange de la competencia: https://unix.stackexchange.com/questions/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something-more
Información sobre el envío en sistemas Windows: http://download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc
La teoría
En teoría, cuando un hilo intenta bloquear un mutex y no tiene éxito, porque el mutex ya está bloqueado, se pondrá en suspensión, lo que inmediatamente permitirá que se ejecute otro hilo. Continuará durmiendo hasta que se despierte, lo que ocurrirá una vez que el mutex esté siendo desbloqueado por el hilo que estaba sosteniendo la cerradura antes. Cuando un hilo intenta bloquear un spinlock y no tiene éxito, volverá a intentarlo continuamente, hasta que finalmente tenga éxito; por lo tanto, no permitirá que otro subproceso tome su lugar (sin embargo, el sistema operativo cambiará forzosamente a otro subproceso, una vez que se haya excedido la cantidad de tiempo de ejecución de la CPU del subproceso actual, por supuesto).
El problema
El problema con las exclusiones mutuas es que poner los subprocesos en suspensión y volver a activarlos son operaciones bastante caras, necesitarán bastantes instrucciones de CPU y, por lo tanto, tomarán algo de tiempo. Si ahora la exclusión mutua solo se bloqueó durante un período de tiempo muy corto, el tiempo empleado en poner un hilo en modo de dormir y en despertarlo nuevamente podría exceder el tiempo que el hilo ha durmido mucho e incluso puede ser mayor que el tiempo que el hilo han desperdiciado por sondeo constantemente en un spinlock. Por otro lado, el sondeo en un spinlock perderá constantemente el tiempo de CPU y si el bloqueo se mantiene durante más tiempo, perderá mucho más tiempo de CPU y habría sido mucho mejor si el hilo estuviera durmiendo.
La solución
El uso de spinlocks en un sistema de un solo núcleo / una sola CPU no tiene sentido, ya que mientras el sondeo de spinlock esté bloqueando el único núcleo de la CPU disponible, no se puede ejecutar ningún otro subproceso y ningún otro subproceso puede ejecutarse, el bloqueo no funcionará. ser desbloqueado tampoco. IOW, un spinlock solo pierde el tiempo de CPU en esos sistemas sin ningún beneficio real. Si el hilo se pusiera en suspensión en su lugar, otro hilo podría haberse ejecutado a la vez, posiblemente desbloqueando el bloqueo y luego permitiendo que el primer hilo continuara procesándose, una vez que se despertó de nuevo.
En sistemas multi-core / multi-CPU, con una gran cantidad de bloqueos que se mantienen solo por un período muy corto de tiempo, el tiempo perdido para poner los subprocesos constantemente en suspensión y volver a activarlos puede disminuir notablemente el rendimiento del tiempo de ejecución. En cambio, al usar spinlocks, los hilos tienen la oportunidad de aprovechar su cantidad de tiempo de ejecución completo (siempre bloqueando solo por un período de tiempo muy corto, pero luego continúan su trabajo de inmediato), lo que lleva a un rendimiento de procesamiento mucho mayor.
La práctica
Dado que muy a menudo los programadores no pueden saber de antemano si los bloqueos mutuos o los spinlocks serán mejores (por ejemplo, porque se desconoce el número de núcleos de CPU de la arquitectura de destino), ni los sistemas operativos pueden saber si una determinada pieza de código se ha optimizado para un solo núcleo o En entornos multi-core, la mayoría de los sistemas no distinguen estrictamente entre mutexes y spinlocks. De hecho, la mayoría de los sistemas operativos modernos tienen mutex híbridos y cierres giratorios híbridos. ¿Qué significa eso realmente?
Un mutex híbrido se comporta como un spinlock al principio en un sistema multi-core. Si un hilo no puede bloquear el mutex, no se pondrá en suspensión inmediatamente, ya que el mutex podría desbloquearse muy pronto, por lo que en su lugar el mutex se comportará exactamente igual que un spinlock. Solo si el bloqueo aún no se ha obtenido después de un cierto tiempo (o reintentos o cualquier otro factor de medición), el hilo realmente se pone en suspensión. Si el mismo código se ejecuta en un sistema con un solo núcleo, el mutex no se bloqueará, sin embargo, como se ve arriba, eso no sería beneficioso.
Un spinlock híbrido se comporta como un spinlock normal al principio, pero para evitar perder demasiado tiempo de CPU, puede tener una estrategia de retroceso. Por lo general, no pondrá el hilo en suspensión (ya que no desea que eso suceda al usar un spinlock), pero puede decidir detener el hilo (ya sea inmediatamente o después de un cierto tiempo) y permitir que se ejecute otro hilo. , lo que aumenta las posibilidades de que el spinlock esté desbloqueado (un cambio de hilo puro suele ser menos costoso que uno que implica poner un hilo para dormir y despertarlo de nuevo más tarde, aunque no mucho).
Resumen
En caso de duda, use mutexes, usualmente son la mejor opción y la mayoría de los sistemas modernos les permitirán bloquear por un período de tiempo muy corto, si esto parece ser beneficioso. El uso de los spinlocks a veces puede mejorar el rendimiento, pero solo bajo ciertas condiciones y el hecho de que tenga dudas me dice que no está trabajando en ningún proyecto en la actualidad donde un spinlock podría ser beneficioso. Podría considerar usar su propio "objeto de bloqueo", que puede usar un spinlock o un mutex internamente (por ejemplo, este comportamiento podría ser configurable al crear dicho objeto), inicialmente use mutexes en todas partes y si piensa que usar un spinlock en algún lugar realmente podría ayuda, pruébalo y compara los resultados (por ejemplo, mediante un generador de perfiles), pero asegúrate de probar ambos casos, un sistema de un solo núcleo y un sistema de varios núcleos antes de saltar a conclusiones (y posiblemente a diferentes sistemas operativos, si tu código será multiplataforma).
Continuando con la sugerencia de Mecki, este artículo pthread mutex vs pthread spinlock en el blog de Alexander Sandler, Alex en Linux muestra cómo se pueden implementar el spinlock
y mutexes
para probar el comportamiento utilizando #ifdef.
Sin embargo, asegúrese de tomar la llamada final en función de su observación, ya que el ejemplo dado es un caso aislado, el requisito de su proyecto, el entorno puede ser completamente diferente.