Almacenamiento local de subprocesos

Método para la gestión de la memoria de la computadora

En programación informática , el almacenamiento local de subprocesos ( TLS ) es un método de gestión de memoria que utiliza memoria estática o global local para un subproceso . El concepto permite el almacenamiento de datos que parecen globales en un sistema con subprocesos separados.

Muchos sistemas imponen restricciones sobre el tamaño del bloque de memoria local del subproceso; de hecho, a menudo imponen límites bastante estrictos. Por otro lado, si un sistema puede proporcionar al menos una dirección de memoria (puntero) de tamaño variable local del subproceso, esto permite el uso de bloques de memoria de tamaño arbitrario de manera local del subproceso, asignando dicho bloque de memoria de forma dinámica y almacenando la dirección de memoria de ese bloque en la variable local del subproceso. En las máquinas RISC , la convención de llamada a menudo reserva un registro de puntero del subproceso para este uso.

Uso

Aunque el uso de variables globales generalmente no se recomienda en la programación moderna, algunos sistemas operativos más antiguos, como UNIX, se diseñaron originalmente para hardware monoprocesador y, a menudo, utilizan variables globales para almacenar valores importantes. Un ejemplo es el que errnoutilizan muchas funciones de la biblioteca C. En una máquina moderna, donde varios subprocesos pueden estar modificando la errnovariable, una llamada a una función del sistema en un subproceso puede sobrescribir el valor establecido previamente por una llamada a una función del sistema en un subproceso diferente, posiblemente antes de que el código siguiente en ese subproceso diferente pueda verificar la condición de error. La solución es tener errnouna variable que parezca global, pero que esté almacenada físicamente en un grupo de memoria por subproceso, el almacenamiento local del subproceso.

Un segundo caso de uso sería el de varios subprocesos que acumulan información en una variable global. Para evitar una condición de carrera , cada acceso a esta variable global tendría que estar protegido por un mutex . En cambio, cada subproceso podría acumular información en una variable local del subproceso, eliminando así cualquier posibilidad de una condición de carrera y, por lo tanto, eliminando la necesidad de bloqueo. Los subprocesos solo tienen que sincronizar una acumulación final desde su propia variable local del subproceso en una única variable global.

Implementación de Windows

La función de interfaz de programación de aplicaciones (API) TlsAllocse puede utilizar para obtener un índice de ranura TLS no utilizado ; el índice de ranura TLS se considerará entonces "usado".

Las funciones TlsGetValuey TlsSetValuese utilizan para leer y escribir una dirección de memoria en una variable local del subproceso identificada por el índice de ranura TLS . TlsSetValuesolo afecta a la variable del subproceso actual. TlsFreeSe puede llamar a la función para liberar el índice de ranura TLS .

Hay un bloque de información de subprocesos Win32 para cada subproceso. Una de las entradas de este bloque es la tabla de almacenamiento local del subproceso para ese subproceso. [1] Cada llamada de TlsAllocdevuelve un índice único en esta tabla. Cada subproceso puede usar y obtener de forma independiente TlsSetValue(index, value)el valor especificado a través de TlsGetValue(index), porque estos establecen y buscan una entrada en la propia tabla del subproceso.

Aparte de la familia de funciones TlsXxx, los ejecutables de Windows pueden definir una sección que se asigna a una página diferente para cada subproceso del proceso en ejecución. A diferencia de los valores TlsXxx, estas páginas pueden contener direcciones arbitrarias y válidas. Sin embargo, estas direcciones son diferentes para cada subproceso en ejecución y, por lo tanto, no se deben pasar a funciones asincrónicas (que pueden ejecutarse en un subproceso diferente) ni a código que suponga que una dirección virtual es única dentro de todo el proceso. Las secciones TLS se administran mediante paginación de memoria y su tamaño se cuantifica a un tamaño de página (4 kB en máquinas x86). Dichas secciones solo se pueden definir dentro de un ejecutable principal de un programa; las DLL no deben contener dichas secciones, porque no se inicializan correctamente al cargar con LoadLibrary.

Implementación de Pthreads

En la API de Pthreads , la memoria local de un hilo se designa con el término Datos específicos del hilo.

Las funciones pthread_key_createy pthread_key_deletese utilizan respectivamente para crear y eliminar una clave para datos específicos de un subproceso. El tipo de la clave se deja explícitamente opaco y se denomina pthread_key_t. Esta clave puede ser vista por todos los subprocesos. En cada subproceso, la clave se puede asociar con datos específicos del subproceso mediante pthread_setspecific. Los datos se pueden recuperar posteriormente utilizando pthread_getspecific.

Además, pthread_key_createse puede aceptar opcionalmente una función destructora que se llamará automáticamente al salir del hilo, si los datos específicos del hilo no son NULL . El destructor recibe el valor asociado a la clave como parámetro para que pueda realizar acciones de limpieza (cerrar conexiones, liberar memoria, etc.). Incluso cuando se especifica un destructor, el programa debe llamar pthread_key_deletepara liberar los datos específicos del hilo a nivel de proceso (el destructor solo libera los datos locales del hilo).

Implementación específica del idioma

Además de confiar en que los programadores llamen a las funciones API adecuadas, también es posible ampliar el lenguaje de programación para admitir el almacenamiento local de subprocesos (TLS).

C y C++

En C11 , la palabra clave _Thread_localse utiliza para definir variables locales del subproceso. El encabezado <threads.h>, si es compatible, define thread_localcomo sinónimo de esa palabra clave. Ejemplo de uso:

#include <threads.h> hilo_local int foo = 0 ;     

En C11, <threads.h>también se definen varias funciones para recuperar, modificar y destruir un almacenamiento local de subprocesos, utilizando nombres que comienzan con tss_. En C23, thread_localse convierte en una palabra clave. [2]

C++11 introduce la palabra clave thread_local[3] que se puede utilizar en los siguientes casos

  • Variables de nivel de espacio de nombres (globales)
  • Variables estáticas de archivo
  • Variables estáticas de función
  • Variables miembro estáticas

Aparte de eso, varias implementaciones del compilador proporcionan formas específicas de declarar variables locales del hilo:

  • Solaris Studio C/C++, [4] IBM XL C/C++, [5] GNU C , [6] llvm-gcc, [7] Clang , [8] e Intel C++ Compiler (sistemas Linux) [9] utilizan la sintaxis:
    __thread int number;
  • Visual C++ , [10] Intel C/C++ (sistemas Windows), [11] C++Builder , Clang, [12] y Digital Mars C++ utilizan la sintaxis:
    __declspec(thread) int number;
  • C++Builder también admite la sintaxis:
    int __thread number;

En versiones de Windows anteriores a Vista y Server 2008, __declspec(thread)funciona en DLL solo cuando dichas DLL están vinculadas al ejecutable y no funcionará para aquellas cargadas con LoadLibrary() (puede ocurrir una falla de protección o corrupción de datos). [10]

Common Lisp y otros dialectos

Common Lisp proporciona una característica llamada variables de ámbito dinámico .

Las variables dinámicas tienen un enlace que es privado para la invocación de una función y todos los hijos llamados por esa función.

Esta abstracción se asigna naturalmente al almacenamiento específico de subprocesos, y las implementaciones de Lisp que proporcionan subprocesos lo hacen. Common Lisp tiene numerosas variables dinámicas estándar, por lo que no se pueden agregar subprocesos a una implementación del lenguaje sin que estas variables tengan semántica local de subprocesos en la vinculación dinámica.

Por ejemplo, la variable estándar *print-base*determina el orden predeterminado en el que se imprimen los números enteros. Si se anula esta variable, todo el código que la incluya imprimirá los números enteros en un orden alternativo:

;;; la función foo y sus hijos imprimirán ;; en hexadecimal: ( let (( *print-base* 16 )) ( foo ))   

Si las funciones pueden ejecutarse simultáneamente en diferentes subprocesos, esta vinculación debe ser local del subproceso; de lo contrario, cada subproceso luchará por quién controla un radio de impresión global.

D

En la versión 2 de D , todas las variables estáticas y globales son locales del subproceso de manera predeterminada y se declaran con una sintaxis similar a las variables globales y estáticas "normales" en otros lenguajes. Las variables globales se deben solicitar explícitamente mediante la palabra clave shared :

int threadLocal ; // Esta es una variable local del hilo. shared int global ; // Esta es una variable global compartida con todos los hilos.     

La palabra clave shared funciona como clase de almacenamiento y como calificador de tipo : las variables compartidas están sujetas a algunas restricciones que imponen estáticamente la integridad de los datos. [13] Para declarar una variable global "clásica" sin estas restricciones, se debe utilizar la palabra clave insegura __gshared : [14]

__gshared int global ; // Esta es una variable global simple y común.   

Java

En Java , las variables locales de subproceso se implementan mediante el objeto ThreadLocal de clase . [15] ThreadLocal contiene una variable de tipo T, [15] a la que se puede acceder mediante métodos get/set. Por ejemplo, la variable ThreadLocal que contiene un valor entero se ve así:

privado estático final ThreadLocal < Integer > myThreadLocalInteger = nuevo ThreadLocal < Integer > ();       

Al menos para Oracle/OpenJDK, esto no utiliza almacenamiento local de subprocesos nativo a pesar de que los subprocesos del sistema operativo se utilizan para otros aspectos de la subprocesación de Java. En cambio, cada objeto Thread almacena un mapa (no seguro para subprocesos) de objetos ThreadLocal con sus valores (en lugar de que cada ThreadLocal tenga un mapa de objetos Thread con sus valores y genere una sobrecarga de rendimiento). [16]

Lenguajes .NET: C# y otros

En lenguajes .NET Framework como C# , los campos estáticos se pueden marcar con el atributo ThreadStatic: [17] : 898 

clase FooBar { [ThreadStatic] privado estático int _foo ; }      

En .NET Framework 4.0, la clase System.Threading.ThreadLocal<T> está disponible para asignar y cargar de forma diferida variables locales de subprocesos. [17] : 899 

clase FooBar { privada estática System.Threading.ThreadLocal < int > _foo ; }     

También hay disponible una API para asignar dinámicamente variables locales de subproceso. [17] : 899–890 

Objeto Pascal

En Object Pascal ( Delphi ) o Free Pascal, la palabra clave reservada threadvar se puede utilizar en lugar de 'var' para declarar variables utilizando el almacenamiento local del hilo.

var mydata_process : entero ; hilovar mydata_threadlocal : entero ;    

Objetivo-C

En Cocoa , GNUstep y OpenStep , cada NSThreadobjeto tiene un diccionario local del hilo al que se puede acceder a través del threadDictionarymétodo del hilo.

NSMutableDictionary * dict = [[ NSThread currentThread ] threadDictionary ]; dict [ @"Una clave" ] = @"Algunos datos" ;       

Perl

En Perl, los subprocesos se añadieron en una fase tardía de la evolución del lenguaje, después de que ya existía una gran cantidad de código en la Red Integral de Archivos de Perl (CPAN). Por tanto, los subprocesos en Perl utilizan de forma predeterminada su propio almacenamiento local para todas las variables, con el fin de minimizar el impacto de los subprocesos en el código existente que no es compatible con subprocesos. En Perl, se puede crear una variable compartida por subprocesos utilizando un atributo:

usar hilos ; usar hilos::compartidos ;  mi $localvar ; mi $sharedvar : compartido ;   

Puro Básico

En PureBasic, las variables de hilo se declaran con la palabra clave Threaded.

Var roscado

Pitón

En la versión 2.4 o posterior de Python , la clase local en el módulo de subprocesos se puede usar para crear almacenamiento local de subprocesos.

importar  threading misdatos  =  threading . local () misdatos . x  =  1

Se pueden crear múltiples instancias de una clase local para almacenar diferentes conjuntos de variables. [18] Por lo tanto, no es un singleton .

Rubí

Ruby puede crear/acceder a variables locales del hilo usando los métodos []=/ []:

Hilo . actual [ :user_id ] = 1  

Óxido

Las variables locales del hilo se pueden crear en Rust usando la thread_local!macro proporcionada por la biblioteca estándar de Rust:

utilizar std :: cell :: RefCell ; utilizar std :: hilo ;  hilo_local! ( estático FOO : RefCell < u32 > = RefCell :: new ( 1 ));   FOO . con ( | f | { assert_eq! ( * f . borrow (), 1 ); * f . borrow_mut () = 2 ; });      // cada hilo comienza con el valor inicial de 1, aunque este hilo ya cambió su copia del valor local del hilo a 2 let t = thread :: spawn ( move || { FOO.with ( | f | { assert_eq ! ( * f.bending ( ) ), 1 ); * f.bending_mut () = 3 ; } ); }) ;             // esperar a que el hilo se complete y salir del pánico t.join (). unwrap ();// el hilo original conserva el valor original de 2 a pesar de que el hilo secundario cambia el valor a 3 para ese hilo FOO . with ( | f | { assert_eq! ( * f . borrow (), 2 ); });   

Véase también

  • OpenMP : otra función de multiprocesamiento de memoria compartida que admite almacenamiento por subproceso a través de "cláusulas de atributos de uso compartido de datos" (consulte §Cláusulas )

Referencias

  1. ^ Pietrek, Matt (mayo de 2006). "Under the Hood". Microsoft Systems Journal . 11 (5). Archivado desde el original el 9 de septiembre de 2010 . Consultado el 6 de abril de 2010 .
  2. ^ "Biblioteca de soporte de concurrencia - cppreference.com". en.cppreference.com .
  3. ^ Sección 3.7.2 del estándar C++11
  4. ^ "Información del compilador de C específica para la implementación de Sun". Guía del usuario de C Sun Studio 8. 2004. 2.3 Especificador de almacenamiento local de subprocesos.
  5. ^ "Compiladores XL C/C++". Agosto de 2010. Almacenamiento local de subprocesos (TLS). Archivado desde el original el 11 de abril de 2011.
  6. ^ "Almacenamiento local de subprocesos". Manual de GCC 3.3.1 . 2003.
  7. ^ "Notas de la versión de LLVM 2.0". 23 de mayo de 2007. Mejoras de llvm-gcc.
  8. ^ "Extensiones del lenguaje Clang - Documentación de Clang 3.8". Introducción. Este documento describe las extensiones del lenguaje proporcionadas por Clang. Además de las extensiones del lenguaje que se enumeran aquí, Clang pretende ofrecer compatibilidad con una amplia gama de extensiones de GCC. Consulte el manual de GCC para obtener más información sobre estas extensiones.
  9. ^ "Notas de la versión del compilador Intel® C++ 8.1 para Linux para procesadores Intel IA-32 e Itanium®" (PDF) . 2004. Almacenamiento local de subprocesos. Archivado desde el original (PDF) el 19 de enero de 2015.
  10. ^ de Visual Studio 2003: "Almacenamiento local de subprocesos (TLS)". Microsoft Docs . 5 de junio de 2017.
  11. ^ Compilador Intel C++ 10.0 (Windows): almacenamiento local de subprocesos
  12. ^ "Atributos en Clang - Documentación de Clang 3.8".
  13. ^ Alexandrescu, Andrei (6 de julio de 2010). "Capítulo 13 - Concurrencia". El lenguaje de programación D. InformIT. pág. 3. Consultado el 3 de enero de 2014 .
  14. ^ Bright, Walter (12 de mayo de 2009). "Migración a Shared". dlang.org . Consultado el 3 de enero de 2014 .
  15. ^ ab Bloch 2018, p. 151-155, §Ítem 33: Considere contenedores heterogéneos de tipos seguros.
  16. ^ "¿Cómo se implementa ThreadLocal de Java?". Stack Overflow . Stack Exchange . Consultado el 27 de diciembre de 2015 .
  17. ^abc Albahari 2022.
  18. ^ "cpython/Lib/_threading_local.py en 3.12 · python/cpython". GitHub . Consultado el 25 de octubre de 2023 .

Bibliografía

  • Albahari, Joseph (2022). Do sostenido 10 en pocas palabras (Primera edición). O'Reilly. ISBN 978-1-098-12195-2.
  • Bloch, Joshua (2018). "Effective Java: Programming Language Guide" (tercera edición). Addison-Wesley. ISBN 978-0134685991.


  • Manejo de ELF para almacenamiento local de subprocesos: documento sobre una implementación en C o C++ .
  • Referencia de plantilla de clase ACE_TSS< TYPE >
  • Documentación de la plantilla de clase RWTThreadLocal<Type>
  • Artículo "Usar almacenamiento local de subprocesos para pasar datos específicos de subprocesos" de Doug Doedens
  • Almacenamiento local de subprocesos, por Lawrence Crowl
  • Artículo “No siempre es bueno compartir” de Walter Bright
  • Uso práctico de ThreadLocal en Java: http://www.captechconsulting.com/blogs/a-persistence-pattern-using-threadlocal-and-ejb-interceptors
  • CCG "[1]"
  • Óxido "[2]"
Retrieved from "https://en.wikipedia.org/w/index.php?title=Thread-local_storage&oldid=1252543227"