stringtranslate.com

Seguridad del hilo

En la programación informática de subprocesos múltiples , una función es segura para subprocesos cuando varios subprocesos pueden invocarla o acceder a ella simultáneamente sin causar comportamientos inesperados, condiciones de carrera o corrupción de datos. [1] [2] Como en el contexto de subprocesos múltiples donde un programa ejecuta varios subprocesos simultáneamente en un espacio de direcciones compartido y cada uno de esos subprocesos tiene acceso a la memoria de todos los demás subprocesos , las funciones seguras para subprocesos deben garantizar que todos esos subprocesos se comporten correctamente y cumplir con sus especificaciones de diseño sin interacción no deseada. [3]

Existen varias estrategias para crear estructuras de datos seguras para subprocesos. [3]

Niveles de seguridad del hilo

Diferentes proveedores utilizan terminología ligeramente diferente para la seguridad de subprocesos, [4] pero la terminología de seguridad de subprocesos más comúnmente utilizada es: [2]

Las garantías de seguridad de subprocesos generalmente también incluyen pasos de diseño para prevenir o limitar el riesgo de diferentes formas de interbloqueos , así como optimizaciones para maximizar el rendimiento concurrente. Sin embargo, no siempre se pueden ofrecer garantías de ausencia de interbloqueos, ya que los interbloqueos pueden ser causados ​​por devoluciones de llamadas y violaciones de capas arquitectónicas independientes de la propia biblioteca.

Las bibliotecas de software pueden proporcionar ciertas garantías de seguridad de subprocesos. [5] Por ejemplo, se puede garantizar que las lecturas simultáneas sean seguras para subprocesos, pero es posible que las escrituras simultáneas no lo sean. Que un programa que utilice dicha biblioteca sea seguro para subprocesos depende de si utiliza la biblioteca de manera coherente con esas garantías.

Enfoques de implementación

A continuación analizamos dos clases de enfoques para evitar condiciones de carrera para lograr la seguridad de los subprocesos.

La primera clase de enfoques se centra en evitar el estado compartido e incluye:

Reentrada [6]
Escribir código de tal manera que pueda ser ejecutado parcialmente por un subproceso, ejecutado por el mismo subproceso o ejecutado simultáneamente por otro subproceso y aún así completar correctamente la ejecución original. Esto requiere guardar información de estado en variables locales para cada ejecución, generalmente en una pila, en lugar de variables estáticas o globales u otros estados no locales. Se debe acceder a todos los estados no locales mediante operaciones atómicas y las estructuras de datos también deben ser reentrantes.
Almacenamiento local de subprocesos
Las variables se localizan para que cada hilo tenga su propia copia privada. Estas variables conservan sus valores a través de subrutinas y otros límites de código y son seguras para subprocesos ya que son locales para cada subproceso, aunque el código que accede a ellas pueda ser ejecutado simultáneamente por otro subproceso.
Objetos inmutables
El estado de un objeto no se puede cambiar después de su construcción. Esto implica que solo se comparten datos de solo lectura y que se logra la seguridad inherente de los subprocesos. Las operaciones mutables (no constantes) se pueden implementar de tal manera que creen nuevos objetos en lugar de modificar los existentes. Este enfoque es característico de la programación funcional y también lo utilizan las implementaciones de cadenas en Java, C# y Python. (Ver Objeto inmutable ).


La segunda clase de enfoques está relacionada con la sincronización y se utiliza en situaciones en las que no se puede evitar el estado compartido:

Exclusión mutua
El acceso a los datos compartidos se serializa mediante mecanismos que garantizan que solo un hilo lea o escriba en los datos compartidos en cualquier momento. La incorporación de la exclusión mutua debe estar bien pensada, ya que el uso inadecuado puede provocar efectos secundarios como interbloqueos , bloqueos en vivo y escasez de recursos .
Operaciones atómicas
Se accede a los datos compartidos mediante operaciones atómicas que no pueden ser interrumpidas por otros subprocesos. Por lo general, esto requiere el uso de instrucciones especiales en lenguaje de máquina , que pueden estar disponibles en una biblioteca en tiempo de ejecución . Dado que las operaciones son atómicas, los datos compartidos siempre se mantienen en un estado válido, sin importar cómo accedan a ellos otros subprocesos. Las operaciones atómicas forman la base de muchos mecanismos de bloqueo de subprocesos y se utilizan para implementar primitivas de exclusión mutua.

Ejemplos

En el siguiente código Java , la palabra clave Java sincronizada hace que el método sea seguro para subprocesos:

clase  Contador { privado int i = 0 ;       público sincronizado void inc () { i ++ ; } }      

En el lenguaje de programación C , cada hilo tiene su propia pila. Sin embargo, una variable estática no se mantiene en la pila; todos los hilos comparten acceso simultáneo a él. Si varios subprocesos se superponen mientras se ejecuta la misma función, es posible que un subproceso cambie una variable estática mientras otro está a mitad de comprobarla. Este error lógico difícil de diagnosticar , que puede compilarse y ejecutarse correctamente la mayor parte del tiempo, se denomina condición de carrera . Una forma común de evitar esto es utilizar otra variable compartida como "bloqueo" o "mutex" (de exclusión mutua ).

En el siguiente fragmento de código C, la función es segura para subprocesos, pero no reentrante:

# incluir <pthread.h>  int increment_counter () { contador int estático = 0 ; estático pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ;             // sólo permite que un hilo se incremente a la vez pthread_mutex_lock ( & ​​mutex );  ++ contador ; // almacena el valor antes de que otros subprocesos lo incrementen más int resultado = contador ;     pthread_mutex_unlock ( & ​​mutex ); resultado de retorno ; } 

En lo anterior, increment_counterpuede ser llamado por diferentes hilos sin ningún problema ya que se utiliza un mutex para sincronizar todos los accesos a la countervariable compartida. Pero si la función se usa en un manejador de interrupciones reentrantes y surge una segunda interrupción mientras el mutex está bloqueado, la segunda rutina se bloqueará para siempre. Como el servicio de interrupciones puede desactivar otras interrupciones, todo el sistema podría verse afectado.

La misma función se puede implementar para que sea segura para subprocesos y reentrante utilizando los átomos sin bloqueo en C++11 :

# incluir <atómico>  int increment_counter () { static std :: atómico < int > contador ( 0 );      // se garantiza que el incremento se realizará de forma atómica int resultado = ++ contador ;     resultado de retorno ; } 

Ver también

Referencias

  1. ^ Kerrisk, Michael (2010). La interfaz de programación de Linux . Sin prensa de almidón . pag. 699, "Capítulo 31: HILOS: SEGURIDAD DE LOS HILOS Y ALMACENAMIENTO POR HILOS"{{cite book}}: CS1 maint: postscript (link)
  2. ^ ab Oráculo (1 de noviembre de 2010). "Oracle: seguridad de subprocesos". Docs.oracle.com . Consultado el 16 de octubre de 2013. "Un procedimiento es seguro para subprocesos cuando el procedimiento es lógicamente correcto cuando lo ejecutan simultáneamente varios subprocesos"; "3 niveles de seguridad para subprocesos"{{cite web}}: CS1 maint: postscript (link)
  3. ^ ab Oracle (noviembre de 2020). "Guía de programación multiproceso: Capítulo 7 Interfaces seguras e inseguras". Documentos de Oracle . Consultado el 30 de abril de 2024 ; "Seguridad del hilo"{{cite web}}: CS1 maint: postscript (link)
  4. ^ "Clasificaciones de seguridad de subprocesos API". IBM. 2023-04-11 . Consultado el 9 de octubre de 2023 .
  5. ^ "Niveles de seguridad MT para bibliotecas". Documentos de Oracle . Consultado el 17 de mayo de 2024 .
  6. ^ "Reentrada y seguridad de subprocesos | Qt 5.6". Proyecto Qt . Consultado el 20 de abril de 2016 .

enlaces externos