Mutex, bloqueo de giro, bloqueo de lectura y escritura, bloqueo pesimista, bloqueo optimista

¡Ciérralo si no estás de acuerdo!


El siguiente artículo es de la cuenta pública de wechat codificación Kobayashi, el autor codificación Kobayashi

Cerraduras de exclusión mutua y cerraduras giratorias: quién se siente más cómodo

Los dos de abajo son "bloqueos de exclusión mutua y bloqueos de giro". Muchos bloqueos avanzados se implementan en base a ellos. Puede pensar en ellos como la base de varios bloqueos, por lo que debemos tener clara la diferencia entre los dos. Y la aplicación.

El propósito del bloqueo es garantizar que solo un subproceso acceda al recurso compartido en cualquier momento, a fin de evitar el problema de confusión de datos compartidos causado por varios subprocesos.

Cuando un subproceso se ha bloqueado, otros subprocesos no se bloquearán. El bloqueo de exclusión mutua y el bloqueo de giro manejan de manera diferente después de la falla de bloqueo:

  • Después de que falla el bloqueo de mutex, el subproceso liberará la CPU y se la dará a otros subprocesos;
  • Después de que el bloqueo de giro no se bloquea, el subproceso estará ocupado esperando hasta obtener el bloqueo; el
    mutex es una especie de "bloqueo exclusivo", por ejemplo, cuando el subproceso A se bloquea con éxito, el subproceso A ha monopolizado el mutex en este tiempo, siempre que el hilo A no suelte el bloqueo en su mano, el hilo B no podrá bloquear el bloqueo, por lo que liberará la CPU y se lo dará a otros hilos. Dado que el hilo B libera la CPU, el código de bloqueo del hilo B natural será bloqueado.

El kernel del sistema operativo realiza el fenómeno de falla y bloqueo del bloqueo mutex. Cuando el bloqueo falla, el kernel pondrá el hilo en un estado de "suspensión". Después de que se libere el bloqueo, el kernel despertará el hilo en el momento apropiado. Cuando el hilo lo adquiera con éxito, podrá continuar con la ejecución. Como se muestra en la siguiente figura:
Inserte la descripción de la imagen aquíPor lo tanto, cuando falla el bloqueo mutex, pasará al modo kernel desde el modo de usuario, y dejará que el kernel cambie de hilo por nosotros. Aunque la dificultad de usar el bloqueo se simplifica, hay una cierta gastos generales de rendimiento.

Entonces, ¿cuál es este costo general? Habrá el costo de dos cambios de contexto de hilo:

Cuando el subproceso no se bloquea, el kernel establecerá el estado del subproceso del estado "en ejecución" al estado "inactivo", y luego cambiará la CPU a otros subprocesos para que se ejecuten;
luego, cuando se libere el bloqueo, el subproceso en el El estado "durmiente" anterior será Cambiar al estado "listo", y luego el kernel cambiará la CPU al subproceso para que se ejecute en el momento adecuado.
¿Cuál es el cambio de contexto del hilo? Cuando dos subprocesos pertenecen al mismo proceso, debido a que la memoria virtual es compartida, los recursos de la memoria virtual permanecen intactos al cambiar, y solo los datos privados, los registros y otros datos no compartidos de los subprocesos deben cambiarse.

Los grandes han contado el tiempo que se tarda en cambiar hacia arriba y hacia abajo. Probablemente sea entre decenas de nanosegundos y unos pocos microsegundos. Si el tiempo de ejecución de su código bloqueado es relativamente corto, entonces el tiempo de cambio de contexto puede ser mayor que el tiempo de ejecución de su código bloqueado. Es incluso más largo.

Por lo tanto, si puede estar seguro de que el tiempo de ejecución del código bloqueado es muy corto, no debe usar un bloqueo mutex, sino un bloqueo de giro; de lo contrario, use un bloqueo mutex.

Los bloqueos giratorios utilizan la función CAS (Comparar e intercambiar) proporcionada por la CPU para completar las operaciones de bloqueo y desbloqueo en el "modo de usuario" sin generar activamente el cambio de contexto de subproceso, por lo que en comparación con los bloqueos mutex, será más rápido y costoso. También más pequeño.

El proceso de bloqueo general consta de dos pasos:

  • El primer paso es verificar el estado de la cerradura, si la cerradura está libre, realice el segundo paso;
  • El segundo paso es configurar el bloqueo para que lo mantenga el hilo actual;

La función CAS fusiona estos dos pasos en una instrucción a nivel de hardware para formar 原子指令, lo que garantiza que estos dos pasos sean inseparables. O los dos pasos se ejecutan a la vez o ninguno de los pasos se ejecuta.

Cuando se usan bloqueos de giro, cuando se produce una competencia de múltiples subprocesos por bloqueos, el subproceso que no se pudo bloquear estará "ocupado esperando" hasta que obtenga el bloqueo. Aquí el "ocupado-espera" puede realizar un whileciclo de espera para lograr, pero es mejor usar la CPU para proporcionar PAUSEinstrucciones para implementar el "ocupado-esperar", porque puede reducir el consumo de energía durante el ciclo de espera.

Un bloqueo de giro es el tipo de bloqueo más simple. Sigue girando y usa ciclos de CPU hasta que el bloqueo está disponible.需要注意,在单核 CPU 上,需要抢占式的调度器(即不断通过时钟中断一个线程,运行其他线程)。否则,自旋锁在单 CPU 上无法使用,因为一个自旋的线程永远不会放弃 CPU。

El bloqueo de giro tiene una sobrecarga baja. Por lo general, el cambio de subprocesos no se producirá activamente en sistemas de varios núcleos. Es adecuado para métodos de programación asíncronos, de rutina y otros que cambian las solicitudes en el modo de usuario. Sin embargo, si el tiempo de ejecución del código bloqueado es demasiado de largo, el hilo giratorio ocupará recursos de la CPU durante mucho tiempo, por lo que el tiempo de giro y el tiempo de ejecución del código bloqueado están en una relación "proporcional". Necesitamos saber esto con claridad.

El nivel de uso de los bloqueos de giro y los bloqueos de exclusión mutua es similar, pero el nivel de implementación es completamente diferente:当加锁失败时,互斥锁用「线程切换」来应对,自旋锁则用「忙等待」来应对。

Estos dos son los métodos de procesamiento más básicos de los bloqueos, y los bloqueos más avanzados elegirán uno de ellos para implementar. Por ejemplo, los bloqueos de lectura y escritura pueden implementarse mediante bloqueos mutex o basados ​​en bloqueos de giro.

Bloqueo de lectura y escritura: ¿Existe una distinción de prioridad entre lectura y escritura?

Por el significado literal del bloqueo de lectura-escritura, también podemos saber que se compone de dos partes: un "bloqueo de lectura" y un "bloqueo de escritura". Si solo lee recursos compartidos, utilice un "bloqueo de lectura" para bloquear , y si desea modificar los recursos compartidos, utilice un "bloqueo de escritura". "Bloqueado.

y entonces,读写锁适用于能明确区分读操作和写操作的场景。

El principio de funcionamiento del bloqueo de lectura y escritura es:

Cuando el "bloqueo de escritura" no está retenido por un subproceso, varios subprocesos pueden mantener bloqueos de lectura al mismo tiempo, lo que mejora en gran medida la eficiencia de acceso a los recursos compartidos. Debido a que el "bloqueo de lectura" se usa para leer recursos compartidos, muchos subprocesos que contienen un read lock al mismo tiempo no destruirá los datos del recurso compartido.
Sin embargo, una vez que el "bloqueo de escritura" es mantenido por el subproceso, la operación de adquisición del bloqueo de lectura del subproceso del lector se bloqueará y la operación de adquisición del bloqueo de escritura de otros subprocesos de escritura también se bloqueará.
Por lo tanto, el bloqueo de escritura es un bloqueo exclusivo, porque solo un subproceso puede mantener el bloqueo de escritura en cualquier momento, similar a las exclusiones mutuas y los bloqueos de giro, mientras que el bloqueo de lectura es un bloqueo compartido porque el bloqueo de lectura puede ser retenido por varios subprocesos en el Mismo tiempo.

Después de conocer el principio de funcionamiento del bloqueo de lectura-escritura, podemos encontrar que读写锁在读多写少的场景,能发挥出优势。

Además, según las diferentes implementaciones, los bloqueos de lectura y escritura se pueden dividir en "bloqueos de prioridad de lectura" y "bloqueos de prioridad de escritura".

La expectativa del bloqueo de prioridad de lectura es que el bloqueo de lectura pueda ser retenido por más subprocesos para mejorar la concurrencia del subproceso del lector. Funciona de la siguiente manera: cuando el subproceso del lector A mantiene por primera vez el bloqueo de lectura, el subproceso del escritor B es adquiriendo el bloqueo de escritura En ese momento, se bloqueará, y en el proceso de bloqueo, el subproceso C del lector subsiguiente aún puede adquirir con éxito el bloqueo de lectura. Finalmente, el subproceso B del escritor puede adquirir con éxito el bloqueo de lectura después de que el subproceso del lector A y C suelte el bloqueo de lectura. Como se muestra en la figura siguiente:
Inserte la descripción de la imagen aquí
El bloqueo de prioridad de escritura es la prioridad para servir el hilo de escritura, y su método de trabajo es: cuando el hilo de lectura A primero mantiene el bloqueo de lectura, el hilo de escritura B se bloqueará al adquirir el bloqueo de escritura, y en el proceso de bloqueo, subsecuente El hilo del lector entrante C fallará al adquirir el bloqueo de lectura, por lo que el hilo del lector C quedará bloqueado en la operación de adquirir el bloqueo de lectura, siempre que el hilo del lector A libere el bloqueo de lectura, el hilo de escritura B puede adquirir con éxito el bloqueo de lectura. Como se muestra en la siguiente figura: El
Inserte la descripción de la imagen aquíbloqueo de prioridad de lectura es mejor para leer la concurrencia de subprocesos, pero no está exento de problemas. Imaginemos que si un hilo lector siempre adquiere un bloqueo de lectura, entonces el hilo escritor nunca obtendrá el bloqueo de escritura, lo que provoca el fenómeno de "inanición" del hilo escritor.

Los bloqueos de escritura primero pueden garantizar que el hilo del escritor no se muera de hambre, pero si siempre hay un hilo del escritor adquiriendo el bloqueo de escritura, el hilo del lector también será "muerto de hambre".

Dado que la otra parte puede morir de hambre independientemente del bloqueo de lectura o escritura de prioridad, entonces no favorecemos a ninguna de las partes y participamos en un "bloqueo justo de lectura y escritura".

公平读写锁比较简单的一种方式是:用队列把获取锁的线程排队,不管是写线程还是读线程都按照先进先出的原则加锁即可,这样读线程仍然可以并发,也不会出现「饥饿」的现象。

Tanto los bloqueos de mutex como los bloqueos de giro son los bloqueos más básicos. Los bloqueos de lectura y escritura se pueden implementar eligiendo uno de estos dos bloqueos según el escenario.

Cerradura optimista y cerradura pesimista: ¿Cuál es la diferencia entre la mentalidad de hacer las cosas?

Las exclusiones mutuas, los bloqueos de giro y los bloqueos de lectura y escritura mencionados anteriormente son bloqueos pesimistas.

La cerradura pesimista es más pesimista, piensa多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁。

Por el contrario, si la probabilidad de que varios subprocesos modifiquen los recursos compartidos al mismo tiempo es relativamente baja, se puede utilizar el bloqueo optimista.

El bloqueo optimista es más optimista. Se asume que la probabilidad de conflicto es muy baja. Funciona de la siguiente manera:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。

Cómo volver a intentarlo después de darse por vencido está estrechamente relacionado con el escenario empresarial. Aunque el costo de volver a intentarlo es alto, es aceptable si la probabilidad de conflicto es lo suficientemente baja.

Se puede ver que la mentalidad de Optimistic Lock es que, independientemente del tres-siete-uno, primero cambia los recursos. Además, encontrarás乐观锁全程并没有加锁,所以它也叫无锁编程。

A continuación se muestra un ejemplo de un escenario: documentación en línea.

Todos sabemos que los documentos en línea pueden ser editados por varias personas al mismo tiempo. Si se usa el bloqueo pesimista, mientras un usuario esté editando el documento, otros usuarios no podrán abrir el mismo documento en este momento. Por supuesto, el la experiencia del usuario no es buena.

La realización de la edición simultánea por parte de varias personas en realidad utiliza el bloqueo optimista, que permite a varios usuarios abrir el mismo documento para editarlo y verificar si el contenido modificado entra en conflicto después de editarlo y enviarlo.

¿Qué cuenta como conflicto? Aquí hay un ejemplo. Por ejemplo, el usuario A primero edita el documento en el navegador, y luego el usuario B abre el mismo documento en el navegador para editarlo, pero el usuario B envía cambios que el usuario A. El usuario A no conoce este proceso. Cuando A envía el contenido modificado, luego habrá conflictos en la modificación paralela entre A y B.

¿Cómo verifica el servidor si hay un conflicto? El esquema habitual es el siguiente:

  • Debido a que la probabilidad de conflicto es relativamente baja, permita que el usuario edite el documento primero, pero el navegador registrará el número de versión del documento devuelto por el servidor al descargar el documento;
    cuando el usuario envía la modificación, la solicitud enviada al servidor traerá la versión del documento original Después de recibir el número, el servidor lo compara con el número de versión actual, si el número de versión es el mismo, la modificación es exitosa, de lo contrario el envío falla.
  • De hecho, nuestro SVN y Git comunes también usan la idea de bloqueo optimista. Primero, deje que el usuario edite el código y luego, al enviarlo, use el número de versión para determinar si hay un conflicto. Si ocurre un conflicto, necesitamos modificarlo nosotros mismos. Vuelva a enviarlo.

Aunque el bloqueo optimista elimina la operación de bloqueo y desbloqueo, una vez que ocurre un conflicto, el costo de volver a intentarlo es muy alto, por lo que只有在冲突概率非常低,且加锁成本非常高的场景时,才考虑使用乐观锁。

para resumir

Durante el proceso de desarrollo, el más común es el bloqueo de mutex. Cuando el bloqueo de mutex falla, usará "cambio de subproceso" para solucionarlo. Cuando el subproceso que no pudo bloquear el bloqueo tiene éxito nuevamente en este proceso, habrá dos El costo del cambio de contexto de subproceso secundario, la pérdida de rendimiento es relativamente grande.

Si sabemos claramente que el tiempo de ejecución del código bloqueado es muy corto, entonces deberíamos elegir un bloqueo de giro con una sobrecarga relativamente pequeña, porque cuando el bloqueo de giro no se bloquea, no generará activamente el cambio de hilo, pero ha estado ocupado Hasta que se adquiere el bloqueo, si el tiempo de ejecución del código bloqueado es corto, el tiempo de espera ocupado es correspondientemente corto.

Si puede distinguir entre operaciones de lectura y escritura, el bloqueo de lectura y escritura es más apropiado, ya que permite que varios subprocesos de lectura mantengan el bloqueo de lectura al mismo tiempo, lo que mejora la concurrencia de lecturas. Según la preferencia del lector o del escritor, se puede dividir en un bloqueo de prioridad de lectura y un bloqueo de prioridad de escritura. El bloqueo de prioridad de lectura es muy concurrente, pero el hilo de escritura se morirá de hambre y el bloqueo de prioridad de escritura será dar prioridad al hilo de escritura, y el hilo de lectura también puede morir de hambre, para evitar el problema de la inanición, hay un bloqueo de lectura-escritura justo, que usa una cola para poner en cola los hilos que solicitan el bloqueo, y garantiza el principio de primero en entrar, primero en salir para bloquear los hilos, lo que garantiza que ciertos hilos no se morirán de hambre y la versatilidad es mejor.

Tanto los bloqueos de mutex como los bloqueos de giro son los bloqueos más básicos. Los bloqueos de lectura y escritura se pueden implementar eligiendo uno de estos dos bloqueos según el escenario.

Además, las exclusiones mutuas, los bloqueos de giro y los bloqueos de lectura y escritura son bloqueos pesimistas. Los bloqueos pesimistas creen que cuando se accede simultáneamente a recursos compartidos, la probabilidad de conflicto puede ser muy alta, por lo que antes de acceder a los recursos compartidos, debe bloquear primero.

Por el contrario, si la probabilidad de conflicto es muy baja al acceder a recursos compartidos de manera concurrente, se puede utilizar el bloqueo optimista. Su método de trabajo es que al acceder a los recursos compartidos, no hay necesidad de bloquear primero. Luego de modificar los recursos compartidos, verificar este período Si hay un conflicto en él, si ningún otro subproceso está modificando el recurso, entonces la operación se completa. Si se encuentra que otros subprocesos han modificado el recurso, la operación se abandona.

Sin embargo, una vez que aumenta la probabilidad de conflicto, no es adecuado utilizar el bloqueo optimista porque el costo de reintento para resolver los conflictos es muy alto.

No importa qué tipo de bloqueo se utilice, el rango de nuestro código de bloqueo debe ser lo más pequeño posible, es decir, la granularidad del bloqueo debe ser pequeña, para que la velocidad de ejecución sea más rápida. Luego, si usa el candado correcto, se acelerará.

Supongo que te gusta

Origin blog.csdn.net/weixin_38640052/article/details/108632310
Recomendado
Clasificación