Spinlock versus semáforo

¿Cuáles son las diferencias básicas entre un semáforo y un locking de giro?

¿Cuándo usaríamos un semáforo sobre un locking giratorio?

Spinlock y semáforo difieren principalmente en cuatro cosas:

1. Qué son
Un spinlock es una posible implementación de un locking, es decir, uno que se implementa mediante la espera ocupada (“spinning”). Un semáforo es una generalización de un locking (o, al revés, un locking es un caso especial de un semáforo). Por lo general, pero no necesariamente , los spinlocks solo son válidos dentro de un proceso, mientras que los semáforos se pueden usar para sincronizar también entre procesos diferentes.

Un locking funciona para la exclusión mutua, es decir, un hilo a la vez puede adquirir el locking y proceder con una “sección crítica” del código. Por lo general, esto significa que el código modifica algunos datos compartidos por varios hilos.
Un semáforo tiene un contador y se permitirá adquirir uno o varios subprocesos, dependiendo del valor que le publiques y (en algunas implementaciones) dependiendo de cuál sea su valor máximo permitido.

En la medida, se puede considerar un locking como un caso especial de un semáforo con un valor máximo de 1.

2. Qué hacen
Como se indicó anteriormente, un spinlock es un locking, y por lo tanto un mecanismo de exclusión mutua (estrictamente de 1 a 1). Funciona al consultar y / o modificar repetidamente una ubicación de memoria, generalmente de forma atómica. Esto significa que la adquisición de un spinlock es una operación “ocupada” que posiblemente quema ciclos de CPU durante mucho tiempo (¡quizás para siempre!) Mientras que efectivamente logra “nada”.
El principal incentivo para tal enfoque es el hecho de que un cambio de contexto tiene una sobrecarga equivalente a girar unos cientos (o quizás miles) veces, por lo que si se puede adquirir un locking quemando unos ciclos de centrifugado, esto puede ser muy bueno en general. más eficiente. Además, para aplicaciones en tiempo real, puede que no sea aceptable bloquear y esperar a que el progtwigdor vuelva a ellos en algún momento lejano en el futuro.

Un semáforo, por el contrario, no gira en absoluto, o solo gira durante un tiempo muy corto (como una optimización para evitar la sobrecarga de syscall). Si no se puede adquirir un semáforo, se bloquea, cediendo el tiempo de CPU a un subproceso diferente que está listo para ejecutarse. Esto puede significar, por supuesto, que pasen unos pocos milisegundos antes de que el hilo se programe nuevamente, pero si esto no es un problema (generalmente no lo es), entonces puede ser un enfoque muy eficiente, conservador de la CPU.

3. Cómo se comportan en presencia de congestión
Es un concepto erróneo común que los espirales o los algoritmos sin cerradura son “generalmente más rápidos”, o que solo son útiles para “tareas muy cortas” (idealmente, ningún objeto de sincronización debería mantenerse durante más tiempo de lo absolutamente necesario).
La única diferencia importante es cómo se comportan los diferentes enfoques en presencia de congestión .

Un sistema bien diseñado normalmente tiene poca o ninguna congestión (esto significa que no todos los hilos intentan adquirir el locking al mismo tiempo). Por ejemplo, uno normalmente no escribiría código que adquiera un locking, luego cargaría medio megabyte de datos comprimidos en zip de la red, decodificaría y analizaría los datos, y finalmente modificaría una referencia compartida (anexar datos a un contenedor, etc.) antes de soltar la cerradura. En cambio, uno adquiriría el locking solo con el fin de acceder al recurso compartido .
Como esto significa que hay mucho más trabajo fuera de la sección crítica que dentro de él, naturalmente la probabilidad de que un hilo esté dentro de la sección crítica es relativamente bajo, y por lo tanto, pocos hilos compiten por el locking al mismo tiempo. Por supuesto, de vez en cuando dos hilos intentarán adquirir el locking al mismo tiempo (¡si esto no pudiera suceder, no necesitarías un candado!), Pero esto es más bien la excepción que la regla en un sistema “saludable” .

En tal caso, un spinlock supera en gran medida a un semáforo porque si no hay congestión de locking, la sobrecarga de adquirir el spinlock es una docena de ciclos en comparación con cientos / miles de ciclos para un cambio de contexto o 10-20 millones de ciclos para perder el rest de un segmento de tiempo.

Por otro lado, dada la alta congestión, o si la cerradura se mantiene durante períodos prolongados (a veces simplemente no puede evitarlo), un spinlock quemará cantidades locos de ciclos de CPU para lograr nada.
Un semáforo (o mutex) es una opción mucho mejor en este caso, ya que permite que un subproceso diferente ejecute tareas útiles durante ese tiempo. O bien, si ningún otro hilo tiene algo útil que hacer, permite que el sistema operativo reduzca la velocidad de la CPU y reduzca el consumo de energía.

Además, en un sistema de un solo núcleo, un spinlock será bastante ineficiente en presencia de congestión de locking, ya que un hilo que gira perderá todo su tiempo esperando un cambio de estado que posiblemente no ocurra (no hasta que se programe el lanzamiento del hilo, que no está progtwigdo; ¡No pasará mientras el hilo de espera se está ejecutando!). Por lo tanto, dada cualquier cantidad de controversia, adquirir el locking toma alrededor de 1 1/2 rebanadas de tiempo en el mejor de los casos (suponiendo que el hilo de liberación sea el siguiente progtwigdo), lo cual no es muy bueno.

4. Cómo se implementan
Normalmente, un semáforo envolverá sys_futex bajo Linux (opcionalmente con un spinlock que se cierra después de unos pocos bashs).
Un spinlock se implementa típicamente usando operaciones atómicas, y sin usar nada provisto por el sistema operativo. En el pasado, esto significaba usar intrínseco de comstackdor o instrucciones de ensamblador no portátiles. Mientras tanto, tanto C ++ 11 como C11 tienen operaciones atómicas como parte del lenguaje, por lo que aparte de la dificultad general de escribir correctamente el código de locking correcto, ahora es posible implementar un código sin locking en un dispositivo completamente portátil y (casi) forma indolora

muy simple, un semáforo es un objeto de sincronización “productivo”, un spinlock es un ‘busywait’. (Hay un poco más para semáforos en que sincronizan varios subprocesos, a diferencia de un mutex o guardia o monitor o sección crítica que protege una región de código de un solo subproceso)

Utilizaría un semáforo en más circunstancias, pero use un locking de giro en el que va a bloquear por un tiempo muy corto. Hay un costo de locking, especialmente si bloquea mucho. En tales casos, puede ser más eficiente hacer spinlock por un tiempo mientras se espera que el recurso protegido se desbloquee. Obviamente, hay un golpe de rendimiento si giras demasiado tiempo.

por lo general, si gira durante más tiempo que un hilo de hilo, entonces debe usar un semáforo.

Más allá de lo que dijeron Yoav Aviram y gbjbaanb, el otro punto clave solía ser que nunca usarías un locking de giro en una máquina de una sola CPU, mientras que un semáforo tendría sentido en una máquina de ese tipo. En la actualidad, con frecuencia es difícil encontrar una máquina sin múltiples núcleos o hyperthreading, o equivalente, pero en las circunstancias en que solo tiene una CPU, debe usar semáforos. (Confío que la razón es obvia. Si la CPU única está ocupada esperando a que algo más libere el locking de giro, pero se está ejecutando en la única CPU, es poco probable que se libere hasta que el proceso o hilo actual sea reemplazado por el O / S, que puede tomar un tiempo y nada útil sucede hasta que ocurre la apropiación).

Desde controladores de dispositivos Linux por Rubinni

A diferencia de los semáforos, los spinlocks se pueden usar en código que no puede dormir, como controladores de interrupción

No soy un experto en kernel pero aquí hay algunos puntos:

Incluso una máquina de uniprocesador puede usar lockings de giro si se habilita la preferencia de kernel al comstackr el kernel. Si la prioridad del kernel está desactivada, spin-lock (quizás) se expande a una statement vacía .

Además, cuando intentamos comparar Semaphore vs Spin-lock, creo que semáforo se refiere al utilizado en kernel, NO el que se usa para IPC (userland).

Básicamente, se usará el locking de giro si la sección crítica es pequeña (más pequeña que la sobrecarga de dormir / despertar) y la sección crítica no llama a nada que pueda dormir. Se usará un semáforo si la sección crítica es más grande y puede dormir.

Raman Chalotra.

Spinlock se refiere a una implementación de locking entre subprocesos usando instrucciones de ensamblaje dependientes de la máquina (como prueba y configuración). Se llama un locking de giro porque el hilo simplemente espera en un bucle (“giros”) comprobando repetidamente hasta que el locking esté disponible (espera ocupada). Los spinlocks se utilizan como sustituto de mutexes, que son una instalación suministrada por los sistemas operativos (no por la CPU), porque los spinlocks funcionan mejor, si se bloquean durante un corto período de tiempo.

Un Semaphor es una instalación suministrada por los sistemas operativos para IPC, por lo que su principal objective es la comunicación entre procesos. Al ser una instalación suministrada por el sistema operativo, su rendimiento no será tan bueno como el de un spinlock para el locking inter-thead (aunque es posible). Los semáforos son mejores para bloquear durante periodos de tiempo más largos.

Dicho esto: la implementación de splinlocks en el ensamblaje es complicada y no portátil.

Me gustaría agregar mis observaciones, más generales y no muy específicas de Linux.

Dependiendo de la architecture de la memoria y las capacidades del procesador, es posible que necesite un locking de giro para implementar un semáforo en un sistema de varios núcleos o multiprocesador, porque en dichos sistemas puede ocurrir una condición de carrera cuando dos o más hilos / procesos quieren para adquirir un semáforo

Sí, si su architecture de memoria ofrece el locking de una sección de memoria por un núcleo / procesador que retrasa todos los demás accesos, y si sus procesadores ofrecen una prueba y conjunto, puede implementar un semáforo sin un locking de giro (¡pero con mucho cuidado! )

Sin embargo, como los sistemas multi-core simples / baratos están diseñados (estoy trabajando en sistemas embebidos), no todas las architectures de memoria admiten tales características multi-core / multiprocesador, solo de prueba y configuración o equivalente. Entonces una implementación podría ser la siguiente:

  • adquirir el locking de giro (ocupado esperando)
  • intenta adquirir el semáforo
  • suelta el spin-lock
  • si el semáforo no se adquirió con éxito, suspenda el hilo actual hasta que se libere el semáforo; de lo contrario, continúe con la sección crítica

La liberación del semáforo debería implementarse de la siguiente manera:

  • adquirir el spin-lock
  • suelta el semáforo
  • suelta el spin-lock

Sí, y para semáforos binarios simples en un nivel de sistema operativo, sería posible usar solo un locking de giro como reemplazo. Pero solo si las secciones de código a proteger son realmente muy pequeñas.

Como se dijo antes, si implementa su propio sistema operativo, asegúrese de tener cuidado. La depuración de tales errores es divertida (mi opinión, no compartida por muchos), pero en su mayoría es muy tediosa y difícil.

Un “mutex” (o “locking de exclusión mutua”) es una señal de que dos o más procesos asíncronos pueden usar para reservar un recurso compartido para uso exclusivo. El primer proceso que obtiene la propiedad del “mutex” también obtiene la propiedad del recurso compartido. Otros procesos deben esperar a que el primer proceso libere su propiedad del “mutex” antes de que intenten obtenerlo.

La primitiva de locking más común en el kernel es el spinlock. El spinlock es un locking de un solo titular muy simple. Si un proceso intenta adquirir un spinlock y no está disponible, el proceso seguirá intentándolo (girando) hasta que pueda adquirir el locking. Esta simplicidad crea un locking pequeño y rápido.

Spinlock se usa solo si estás seguro de que el resultado esperado se producirá muy pronto, antes de que expire el tiempo de ejecución del subproceso de ejecución del subproceso.

Ejemplo: en el módulo del controlador del dispositivo, el controlador escribe “0” en el registro de hardware R0 y ahora necesita esperar que el registro R0 se convierta en 1. El H / W lee el R0 y hace algo de trabajo y escribe “1” en R0. Esto es generalmente rápido (en micro segundos). Ahora girar es mucho mejor que ir a dormir e interrumpido por el H / W. ¡Por supuesto, mientras gira, la condición de falla H / W debe ser atendida!

No hay absolutamente ninguna razón para que una aplicación de usuario gire. No tiene sentido. Va a girar para que algún evento suceda y ese evento debe completarse con otra aplicación de nivel de usuario que nunca se garantiza que suceda dentro de un marco de tiempo rápido. Por lo tanto, no giraré en modo de usuario. Mejor para dormir () o mutexlock () o semáforo de locking () en modo de usuario.

¿Cuál es la diferencia entre las cerraduras giratorias y los semáforos? por Maciej Piechotka :

Ambos manejan un recurso limitado. Primero describiré la diferencia entre el semáforo binario (mutex) y el locking de giro.

Los lockings de giro realizan una espera ocupada, es decir, siguen funcionando en bucle:

 while (try_acquire_resource ()); 
  ...  
 lanzamiento(); 

Realiza un locking / deslocking muy ligero, pero si el hilo de locking será reemplazado por otro que intentará acceder al mismo recurso, el segundo simplemente intentará adquirir recursos hasta que se agoten los cuantos de la CPU.
Por otro lado, mutex se comporta más como:

 if (! try_lock ()) {
     add_to_waiting_queue ();
     Espere();
 }
 ...
 proceso * p = get_next_process_from_waiting_queue ();
 p-> wakeUp ();

Por lo tanto, si el hilo intentará adquirir un recurso bloqueado, se suspenderá hasta que esté disponible para él. Bloquear / desbloquear es mucho más pesado, pero la espera es ‘libre’ y ‘justa’.

El semáforo es un candado que se puede usar varias veces (conocido desde la inicialización), por ejemplo, 3 subprocesos pueden albergar simultáneamente el recurso, pero no más. Se usa, por ejemplo, en problemas de productor / consumidor o, en general, en colas:

 P (resources_sem)
 resource = resources.pop ()
 ...
 resources.push (recursos)
 V (resources_sem)

¿Diferencia entre semáforo, mutex y spinlock?

Bloqueando en Linux