En programación de computadoras , el almacenamiento local de subprocesos ( TLS ) es un método de administración de memoria que utiliza memoria estática o global local para un subproceso . El concepto permite el almacenamiento de datos que parecen ser 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 son límites bastante estrictos. Por otro lado, si un sistema puede proporcionar al menos una variable local de subproceso del tamaño de una dirección de memoria (puntero), entonces esto permite el uso de bloques de memoria de tamaño arbitrario de manera local de subproceso, asignando dicho bloque de memoria dinámicamente y almacenándolo. la dirección de memoria de ese bloque en la variable local del subproceso. En máquinas RISC , la convención de llamada a menudo reserva un registro de puntero de hilo para este uso.
Si bien generalmente se desaconseja el uso de variables globales 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 errno
utilizado por muchas funciones de la biblioteca C. En una máquina moderna, donde varios subprocesos pueden estar modificando la errno
variable, una llamada de una función del sistema en un subproceso puede sobrescribir el valor previamente establecido por una llamada de una función del sistema en un subproceso diferente, posiblemente antes de que se pueda seguir el código en ese subproceso diferente. verifique la condición de error. La solución es tener errno
una variable que parezca global, pero que esté físicamente almacenada en un grupo de memoria por subproceso, el almacenamiento local del subproceso.
Un segundo caso de uso sería el de múltiples subprocesos que acumulan información en una variable global. Para evitar una condición de carrera , cada acceso a esta variable global debería estar protegido por un mutex . En cambio, cada subproceso podría acumularse en una variable local del subproceso, eliminando así cualquier posibilidad de una condición de carrera y eliminando así la necesidad de bloqueo. Luego, los subprocesos solo tienen que sincronizar una acumulación final de 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á "usado".
Las funciones TlsGetValue
y TlsSetValue
luego se utilizan para leer y escribir una dirección de memoria en una variable local de subproceso identificada por el índice de ranura TLS . TlsSetValue
sólo afecta a la variable del hilo 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 de subprocesos para ese subproceso. [1] TlsAlloc devuelve un índice a esta tabla, único por espacio de direcciones, para cada llamada. Cada subproceso tiene su propia copia de la tabla de almacenamiento local del subproceso. Por lo tanto, cada hilo puede usar TlsSetValue(index) de forma independiente y obtener el valor especificado a través de TlsGetValue(index), porque estos establecen y buscan una entrada en la propia tabla del hilo.
Además 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 hilo del proceso en ejecución. A diferencia de los valores TlsXxx, estas páginas pueden contener direcciones arbitrarias y válidas. Estas direcciones, sin embargo, son diferentes para cada subproceso en ejecución y, por lo tanto, no deben pasarse a funciones asincrónicas (que pueden ejecutarse en un subproceso diferente) ni pasarse a código que asume 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 al tamaño de una página (4kB en máquinas x86). Dichas secciones solo pueden definirse dentro de un ejecutable principal de un programa; las DLL no deben contener dichas secciones, porque no se inicializan correctamente cuando se cargan con LoadLibrary.
En la API de Pthreads , la memoria local de un subproceso se designa con el término datos específicos del subproceso.
Las funciones pthread_key_create
y pthread_key_delete
se utilizan respectivamente para crear y eliminar una clave para datos específicos del hilo. El tipo de clave se deja explícitamente opaco y se denomina pthread_key_t
. Esta clave puede ser vista por todos los hilos. En cada hilo, la clave se puede asociar con datos específicos del hilo a través de pthread_setspecific
. Los datos se pueden recuperar posteriormente usando pthread_getspecific
.
Además, pthread_key_create
opcionalmente, puede aceptar una función destructora que se llamará automáticamente al salir del subproceso, si los datos específicos del subproceso no son NULL . El destructor recibe el valor asociado a la clave como parámetro para poder realizar acciones de limpieza (cerrar conexiones, liberar memoria, etc.). Incluso cuando se especifica un destructor, el programa aún debe llamar pthread_key_delete
para liberar los datos específicos del subproceso a nivel de proceso (el destructor solo libera los datos locales del subproceso).
Además de depender de los programadores para llamar 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 de subproceso. El encabezado <threads.h>
, si es compatible, se define thread_local
como sinónimo de esa palabra clave. Uso de ejemplo:
#include <threads.h> thread_local int foo = 0 ;
En C11, <threads.h>
también se define una serie de funciones para recuperar, cambiar y destruir un almacenamiento local de subprocesos, utilizando nombres que comienzan con tss_
. En C23, thread_local
ella misma 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 de subproceso:
__thread int number;
__declspec(thread) int number;
int __thread number;
En versiones de Windows anteriores a Vista y Server 2008, __declspec(thread)
funciona en archivos DLL solo cuando esos archivos están vinculados al ejecutable y no funcionará para aquellos cargados 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 elementos secundarios llamados por esa función.
Esta abstracción se asigna naturalmente al almacenamiento específico de subprocesos, y las implementaciones Lisp que proporcionan subprocesos hacen esto. Common Lisp tiene numerosas variables dinámicas estándar, por lo que no se pueden agregar subprocesos de manera sensata a una implementación del lenguaje sin que estas variables tengan una semántica local de subprocesos en enlace dinámico.
Por ejemplo, la variable estándar *print-base*
determina la base predeterminada en la que se imprimen los números enteros. Si se anula esta variable, todo el código adjunto imprimirá números enteros en una base alternativa:
;;; la función foo y sus hijos imprimirán ;; en hexadecimal: ( let (( *print-base* 16 )) ( foo ))
Si las funciones se pueden ejecutar simultáneamente en diferentes subprocesos, este enlace debe ser localmente adecuado al subproceso; de lo contrario, cada subproceso peleará por quién controla una base de impresión global.
En D versión 2, todas las variables estáticas y globales son locales de subproceso de forma predeterminada y se declaran con una sintaxis similar a las variables globales y estáticas "normales" en otros lenguajes. Las variables globales deben solicitarse explícitamente utilizando la palabra clave compartida :
int hiloLocal ; // Esta es una variable local de subproceso. compartido int global ; // Esta es una variable global compartida con todos los hilos.
La palabra clave compartida 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 antigua y sencilla.
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 = new 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 los subprocesos de Java. En cambio, cada objeto Thread almacena un mapa (no seguro para subprocesos) de objetos ThreadLocal con sus valores (a diferencia de cada ThreadLocal que tiene un mapa de objetos Thread con valores e incurre en una sobrecarga de rendimiento). [dieciséis]
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 { sistema estático privado . Enhebrado . ThreadLocal < int > _foo ; }
También hay una API disponible para asignar dinámicamente variables locales de subprocesos. [17] : 899–890
En Object Pascal ( Delphi ) o Free Pascal, la palabra clave reservada threadvar se puede usar en lugar de 'var' para declarar variables usando el almacenamiento local de subprocesos.
var mydata_process : entero ; threadvar mydata_threadlocal : entero ;
En Cocoa , GNUstep y OpenStep , cada NSThread
objeto tiene un diccionario local de subproceso al que se puede acceder a través del threadDictionary
método del subproceso.
NSMutableDictionary * dict = [[ NSThread currentThread ] threadDictionary ]; dict [ @"Una clave" ] = @"Algunos datos" ;
En Perl, los subprocesos se agregaron en una etapa tardía de la evolución del lenguaje, después de que una gran cantidad de código existente ya estuviera presente en Comprehensive Perl Archive Network (CPAN). Por lo tanto, los subprocesos en Perl de forma predeterminada toman su propio almacenamiento local para todas las variables, para minimizar el impacto de los subprocesos en el código existente que no reconoce subprocesos. En Perl, se puede crear una variable compartida en un hilo utilizando un atributo:
utilizar hilos ; utilizar hilos::compartidos ; mi $localvar ; mi $sharedvar : compartido ;
En PureBasic, las variables de subproceso se declaran con la palabra clave Threaded.
Var roscada
En Python versión 2.4 o posterior, la clase local en el módulo de subprocesos se puede utilizar para crear almacenamiento local de subprocesos.
importar subprocesos mydata = subprocesos . local () mis datos . x = 1
Se pueden crear varias instancias de clase local para almacenar diferentes conjuntos de variables. [18] Por lo tanto, no es un singleton .
Ruby puede crear/acceder a variables locales de subprocesos utilizando métodos []=/[]:
Hilo . actual [ :user_id ] = 1
Las variables locales de subproceso se pueden crear en Rust utilizando la thread_local!
macro proporcionada por la biblioteca estándar de Rust:
utilice std :: celda :: RefCell ; utilizar std :: hilo ; hilo_local! ( FOO estático : RefCell <u32> = RefCell :: nuevo ( 1 ) ) ; FOO . con ( | f | { afirmar_eq! ( * f . pedir prestado (), 1 ); * f . pedir prestado_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 | { afirmar_eq! ( * f . pedir prestado (), 1 ); * f . pedir prestado () = 3 } } ); // espera a que se complete el hilo y sal del pánico t . unirse (). desenvolver ();// 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 . con ( | f | { afirmar_eq! ( * f . pedir prestado (), 2 ); });
Este documento describe las extensiones de idioma proporcionadas por Clang. Además de las extensiones de idiomas enumeradas aquí, Clang pretende admitir una amplia gama de extensiones de GCC. Consulte el manual de GCC para obtener más información sobre estas extensiones.
{{cite book}}
: |website=
ignorado ( ayuda )