¿Cuándo se debe usar un spinlock en lugar de mutex?

Respuestas:

729

La teoría

En teoría, cuando un hilo intenta bloquear un mutex y no tiene éxito, porque el mutex ya está bloqueado, se irá a dormir, permitiendo inmediatamente que se ejecute otro hilo. Continuará durmiendo hasta que se despierte, lo que será el caso una vez que el mutex esté desbloqueado por cualquier hilo que haya mantenido el bloqueo antes. Cuando un hilo intenta bloquear un spinlock y no tiene éxito, volverá a intentar bloquearlo 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 los mutexes es que poner los hilos en suspensión y volverlos a activar son operaciones bastante costosas, necesitarán muchas instrucciones de la CPU y, por lo tanto, también tomarán un tiempo. Si ahora el mutex solo estuvo bloqueado durante un período de tiempo muy corto, el tiempo dedicado a poner un hilo a dormir y despertarlo nuevamente podría exceder el tiempo que el hilo realmente ha dormido por mucho tiempo e incluso podría exceder el tiempo que el hilo haber perdido por sondear constantemente en un spinlock. Por otro lado, el sondeo en un spinlock desperdiciará constantemente el tiempo de la CPU y si el bloqueo se mantiene durante un período de tiempo más largo, esto desperdiciará mucho más tiempo de la CPU y hubiera sido mucho mejor si el hilo estuviera dormido.

La solución

El uso de spinlocks en un sistema de núcleo único / CPU simple generalmente no tiene sentido, ya que mientras el sondeo de spinlock esté bloqueando el único núcleo de CPU disponible, no se puede ejecutar ningún otro subproceso y dado que ningún otro subproceso puede ejecutarse, el bloqueo no ser desbloqueado tampoco. IOW, un spinlock desperdicia solo el tiempo de CPU en esos sistemas sin beneficio real. Si el subproceso se puso en reposo, otro subproceso podría haberse ejecutado a la vez, posiblemente desbloqueando el bloqueo y luego permitiendo que el primer subproceso continúe procesándose, una vez que se despertó nuevamente.

En un sistema multi-core / multi-CPU, con un montón de bloqueos que se mantienen solo durante un período de tiempo muy corto, el tiempo perdido para poner constantemente los hilos a dormir y despertarlos nuevamente podría disminuir notablemente el rendimiento del tiempo de ejecución. Cuando se usan spinlocks, los subprocesos tienen la oportunidad de aprovechar su cuántica de tiempo de ejecución completo (siempre solo bloquean durante un período de tiempo muy corto, pero luego continúan su trabajo de inmediato), lo que conduce a un rendimiento de procesamiento mucho mayor.

La práctica

Dado que muy a menudo los programadores no pueden saber de antemano si los mutexes 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 un cierto código ha sido optimizado para un solo núcleo o entornos multinúcleo, la mayoría de los sistemas no distinguen estrictamente entre mutexes y spinlocks. De hecho, la mayoría de los sistemas operativos modernos tienen mutexes híbridos y spinlocks híbridos. ¿Qué significa eso realmente?

Un mutex híbrido se comporta como un spinlock al principio en un sistema multinúcleo. Si un hilo no puede bloquear el mutex, no se pondrá a dormir inmediatamente, ya que el mutex podría desbloquearse muy pronto, por lo que el mutex primero se comportará exactamente como un spinlock. Solo si el bloqueo aún no se ha obtenido después de un cierto período de tiempo (o reintentos o cualquier otro factor de medición), el hilo realmente se pone a dormir. Si el mismo código se ejecuta en un sistema con un solo núcleo, el mutex no girará, aunque, 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 cuando use un spinlock), pero puede decidir detener el hilo (ya sea inmediatamente o después de un cierto período de tiempo) y permitir que se ejecute otro hilo , lo que aumenta las posibilidades de que el spinlock esté desbloqueado (un interruptor de hilo puro suele ser menos costoso que uno que implica poner un hilo a dormir y despertarlo más tarde, aunque no con mucha diferencia).

Resumen

En caso de duda, use mutexes, generalmente son la mejor opción y la mayoría de los sistemas modernos les permitirán hacer spinlock por un período de tiempo muy corto, si esto parece beneficioso. El uso de spinlocks a veces puede mejorar el rendimiento, pero solo bajo ciertas condiciones y el hecho de que tenga dudas, más bien me dice que no está trabajando en ningún proyecto en el que un spinlock pueda ser beneficioso. Puede 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 cree que usar un spinlock en algún lugar podría realmente ayuda, pruébelo y compare los resultados (por ejemplo, usando un generador de perfiles), pero asegúrese de probar ambos casos,

Actualización: una advertencia para iOS

En realidad, no es específico de iOS, pero iOS es la plataforma donde la mayoría de los desarrolladores pueden enfrentar ese problema: si su sistema tiene un programador de subprocesos, eso no garantiza que cualquier subproceso, sin importar cuán baja sea su prioridad, eventualmente tendrá la oportunidad de ejecutarse, entonces los spinlocks pueden conducir a puntos muertos permanentes. El planificador de iOS distingue diferentes clases de subprocesos y los subprocesos en una clase inferior solo se ejecutarán si ningún subproceso en una clase superior quiere ejecutarse también. No hay una estrategia de retroceso para esto, por lo que si tiene permanentemente subprocesos de clase alta disponibles, los subprocesos de clase baja nunca tendrán tiempo de CPU y, por lo tanto, nunca tendrán la oportunidad de realizar ningún trabajo.

El problema aparece de la siguiente manera: su código obtiene un spinlock en un subproceso de clase prio baja y, mientras está en el medio de ese bloqueo, el tiempo cuántico ha excedido y el subproceso deja de funcionar. La única forma en que este spinlock puede liberarse nuevamente es si ese subproceso de clase prio baja obtiene tiempo de CPU nuevamente, pero no se garantiza que esto suceda. Es posible que tenga un par de subprocesos de clase prio alta que desean ejecutarse constantemente y el programador de tareas siempre priorizará esos. Uno de ellos puede atravesar el spinlock y tratar de obtenerlo, lo cual no es posible, por supuesto, y el sistema lo hará ceder. El problema es: ¡un subproceso que produjo está inmediatamente disponible para ejecutarse nuevamente! Al tener un prio más alto que el subproceso que mantiene el bloqueo, el subproceso que mantiene el bloqueo no tiene posibilidad de obtener tiempo de ejecución de la CPU.

¿Por qué este problema no ocurre con mutexes? Cuando el hilo de prio alto no puede obtener el mutex, no cederá, puede girar un poco pero eventualmente será enviado a dormir. Un subproceso inactivo no está disponible para ejecutarse hasta que lo despierte un evento, por ejemplo, un evento como el mutex desbloqueado que estaba esperando. Apple es consciente de ese problema y, por lo tanto, ha quedado OSSpinLocken desuso . Se llama a la nueva cerradura os_unfair_lock. Este bloqueo evita la situación mencionada anteriormente, ya que conoce las diferentes clases de prioridad de subprocesos. Si está seguro de que usar spinlocks es una buena idea en su proyecto de iOS, use ese. Alejate deOSSpinLock! ¡Y bajo ninguna circunstancia implemente sus propios spinlocks en iOS! Si tiene dudas, ¡use un mutex! macOS no se ve afectado por este problema, ya que tiene un programador de subprocesos diferente que no permitirá que ningún subproceso (incluso subprocesos de bajo nivel de prio) se "ejecute en seco" en el tiempo de CPU, pero la misma situación puede surgir allí y luego conducirá a muy mal rendimiento, por lo tanto, OSSpinLockestá en desuso en macOS también.

Mecki
fuente
3
excelente explicación ... Tengo una duda con respecto al spinlock i, ¿puedo usar un spinlock en ISR? si no por qué no
haris
44
@Mecki Si no me equivoco, creo que sugirió en su respuesta que la división de tiempo solo ocurre en sistemas de un solo procesador. ¡Esto no es correcto! Puede usar un bloqueo de giro en un sistema de procesador único y girará hasta que expire su tiempo cuántico. Luego, otro hilo de la misma prioridad puede hacerse cargo (al igual que lo que describió para los sistemas multiprocesador).
fumoboy007
77
@ fumoboy007 "y girará hasta que caduque su tiempo cuántico" // Lo que significa que desperdicia el tiempo de CPU / batería sin absolutamente nada sin ningún beneficio, lo cual es completamente estúpido. Y no, en ninguna parte dije que la división del tiempo solo ocurre en los sistemas de un solo núcleo, dije que en los sistemas de un solo núcleo hay SOLAMENTE la división del tiempo, mientras que hay un paralelismo REAL en los sistemas multinúcleo (y también la división del tiempo, pero irrelevante para lo que escribí en mi respuesta); También se perdió por completo el punto de lo que es un spinlock híbrido y por qué funciona bien en sistemas únicos y multinúcleo.
Mecki
11
@ fumoboy007 El hilo A mantiene el bloqueo y se interrumpe. El subproceso B se ejecuta y quiere el bloqueo, pero no puede obtenerlo, por lo que gira. En un sistema multinúcleo, el subproceso A puede continuar ejecutándose en otro núcleo mientras el subproceso B sigue girando, liberar el bloqueo y el subproceso B puede continuar dentro de su cantidad de tiempo actual. En un sistema de núcleo único, solo se puede ejecutar un subproceso central A para liberar el bloqueo y este subproceso se mantiene ocupado mediante el giro del subproceso B. Por lo tanto, no hay forma de que el spinlock se pueda liberar antes de que el Thread B haya excedido su tiempo cuántico y, por lo tanto, todo el spinning es solo una pérdida de tiempo.
Mecki
2
Si desea obtener más información sobre spinlocks y mutexes implementados en el kernel de Linux, le recomiendo leer el capítulo 5 de excelentes controladores de dispositivos Linux, tercera edición (LDD3) (mutexes: página 109; spinlocks: página 116).
patryk.beza
7

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 puede implementar el spinlock& mutexespara probar el comportamiento usando #ifdef.

Sin embargo, asegúrese de atender 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.

La seda de algodón
fuente
6

Tenga en cuenta también que en ciertos entornos y condiciones (como ejecutarse en Windows en el nivel de despacho> = NIVEL DE ENVÍO), no puede usar mutex sino spinlock. En Unix, lo mismo.

Aquí hay una pregunta equivalente en el sitio de Unix de stackexchange de la competencia: /unix/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something- más

Información sobre despacho en sistemas Windows: http://download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc

Dan Jobs
fuente
6

La respuesta de Mecki lo clava bastante bien. Sin embargo, en un único procesador, el uso de un spinlock puede tener sentido cuando la tarea está esperando que la rutina de servicio de interrupción proporcione el bloqueo. La interrupción transferiría el control al ISR, que prepararía el recurso para su uso en la tarea de espera. Terminaría liberando el bloqueo antes de devolver el control a la tarea interrumpida. La tarea de giro encontraría el spinlock disponible y continuaría.

AlanC
fuente
2
No estoy seguro de estar totalmente de acuerdo con esta respuesta. Un procesador único, si una tarea mantiene un bloqueo en un recurso, el ISR no puede proceder de manera segura y no puede esperar a que la tarea desbloquee el recurso (ya que la tarea que lo contenía se interrumpió). En tales casos, la tarea simplemente debe deshabilitar las interrupciones para forzar la exclusión entre sí y el ISR. Por supuesto, esto tendría que hacerse por intervalos de tiempo muy cortos.
user1202136
3

Los mecanismos de sincronización Spinlock y Mutex son muy comunes hoy en día.

Pensemos primero en Spinlock.

Básicamente es una acción de espera ocupada, lo que significa que tenemos que esperar a que se libere un bloqueo especificado antes de poder continuar con la siguiente acción. Conceptualmente muy simple, mientras que la 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 lidiar con él? ¿Cómo lidiar con los bloqueos de sincronización cuando dos hilos solicitan acceso simultáneamente?

En general, la idea más intuitiva es tratar con la sincronización a través de una variable para proteger la sección crítica. El concepto de Mutex es similar, pero aún son diferentes. Centrarse en: utilización de la CPU. Spinlock consume tiempo de CPU para esperar a que se realice 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 dedicado a la sección crítica es pequeño, utilice Spinlock, porque podemos reducir el tiempo de cambio de contexto. (La comparación de un solo núcleo no es importante, porque la implementación de algunos sistemas Spinlock en el medio del interruptor)

En Windows, el uso de 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).

Marcus Thornton
fuente
-6

El uso de spinlocks en un sistema de núcleo único / CPU simple generalmente no tiene sentido, ya que mientras el sondeo de spinlock esté bloqueando el único núcleo de CPU disponible, no se puede ejecutar ningún otro subproceso y dado que ningún otro subproceso puede ejecutarse, el bloqueo no ser desbloqueado tampoco. IOW, un spinlock desperdicia solo el tiempo de CPU en esos sistemas sin beneficio real

Esto está mal. No hay desperdicio de ciclos de CPU en el uso de spinlocks en sistemas de procesador uni, porque una vez que un proceso toma un bloqueo de giro, la preferencia se desactiva, por lo tanto, ¡no podría haber nadie más girando! ¡Es solo que usarlo no tiene ningún sentido! Por lo tanto, los núcleos giratorios de los sistemas Uni se reemplazan por preempt_disable en tiempo de compilación.

Neelansh Mittal
fuente
Lo citado sigue siendo completamente cierto. Si el resultado compilado del código fuente no contiene spinlocks, entonces la cita es irrelevante. Suponiendo que lo que usted dijo es cierto sobre el kernel que reemplaza los spinlocks en tiempo de compilación, ¿cómo se manejan los spinlocks cuando se precompilan en otra máquina que puede o no ser uniprocesador, a menos que estemos hablando estrictamente de spinlocks solo en el núcleo mismo?
Hydranix
"Una vez que un proceso toma un bloqueo de giro, la preferencia se deshabilita". La aprobación previa no se desactiva cuando un proceso da un giro. Si ese fuera el caso, un solo proceso podría derribar toda la máquina con solo ingresar un spinlock y nunca salir. Tenga en cuenta que si su hilo se ejecuta en el espacio del kernel (en lugar del espacio del usuario), tomar un bloqueo de giro deshabilita la preferencia, pero no creo que eso sea lo que se está discutiendo aquí.
Konstantin Weitz
En tiempo de compilación por el núcleo ?
Shien
@konstantin FYI spin lock solo se puede tomar en el espacio del kernel. Y cuando se toma un bloqueo de giro, la preferencia se deshabilita en el procesador local.
Neelansh Mittal
@hydranix ¿No lo entendió? Obviamente no puede compilar un módulo contra un kernel en el que CONFIG_SMP está habilitado, y ejecutar el mismo módulo en un kernel que tiene CONFIG_SMP deshabilitado.
Neelansh Mittal