Este artículo necesita citas adicionales para su verificación . ( diciembre de 2012 ) |
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.
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 errno
utilizan muchas funciones de la biblioteca C. En una máquina moderna, donde varios subprocesos pueden estar modificando la errno
variable, 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 errno
una 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.
La función de interfaz de programación de aplicaciones (API) TlsAlloc
se puede utilizar para obtener un índice de ranura TLS no utilizado ; el índice de ranura TLS se considerará entonces "usado".
Las funciones TlsGetValue
y TlsSetValue
se utilizan para leer y escribir una dirección de memoria en una variable local del subproceso identificada por el índice de ranura TLS . TlsSetValue
solo afecta a la variable del subproceso actual. TlsFree
Se 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 TlsAlloc
devuelve 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.
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_create
y pthread_key_delete
se 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_create
se 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_delete
para liberar los datos específicos del hilo a nivel de proceso (el destructor solo libera los datos locales del hilo).
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).
En C11 , la palabra clave _Thread_local
se utiliza para definir variables locales del subproceso. El encabezado <threads.h>
, si es compatible, define thread_local
como 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_local
se convierte en una palabra clave. [2]
C++11 introduce la palabra clave thread_local
[3] que se puede utilizar en los siguientes casos
Aparte de eso, varias implementaciones del compilador proporcionan formas específicas de declarar variables locales del hilo:
__thread int number;
__declspec(thread) int number;
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 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.
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.
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]
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
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 ;
En Cocoa , GNUstep y OpenStep , cada NSThread
objeto tiene un diccionario local del hilo al que se puede acceder a través del threadDictionary
método del hilo.
NSMutableDictionary * dict = [[ NSThread currentThread ] threadDictionary ]; dict [ @"Una clave" ] = @"Algunos datos" ;
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 ;
En PureBasic, las variables de hilo se declaran con la palabra clave Threaded.
Var roscado
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 .
Ruby puede crear/acceder a variables locales del hilo usando los métodos []=
/ []
:
Hilo . actual [ :user_id ] = 1
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 ); });
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.