Creo que ambos están haciendo el mismo trabajo, ¿cómo decides cuál usar para la sincronización?
Creo que ambos están haciendo el mismo trabajo, ¿cómo decides cuál usar para la sincronización?
Respuestas:
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,
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 OSSpinLock
en 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, OSSpinLock
está en desuso en macOS también.
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
& mutexes
para 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.
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
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.
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).
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.