El almacenamiento local de subprocesos ( TLS ) es un método de programación de computadoras que utiliza memoria local estática o global para un subproceso .
Si bien el uso de variables globales generalmente se desaconseja en la programación moderna, los sistemas operativos heredados como UNIX están diseñados para hardware monoprocesador y requieren algún mecanismo adicional para retener la semántica de las API pre- reentrantes . Un ejemplo de tales situaciones es cuando las funciones utilizan una variable global para establecer una condición de error (por ejemplo, la variable global errno
utilizada por muchas funciones de la biblioteca C). Si errno
fuera una variable global, una llamada a 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 seguir el código en ese subproceso diferente para verificar la condición de error. La solución es tenererrno
ser una variable que parece global, pero de hecho existe una vez por hilo, es decir, vive en el almacenamiento local del hilo. Un segundo caso de uso sería que varios subprocesos acumulen 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 . Alternativamente, cada subproceso podría acumularse en una variable local de subproceso (que, por definición, no se puede leer ni escribir en otros subprocesos, lo que implica que no puede haber condiciones de carrera). Los subprocesos solo tienen que sincronizar una acumulación final de su propia variable local de subproceso en una única variable verdaderamente global.
Muchos sistemas imponen restricciones sobre el tamaño del bloque de memoria local de subprocesos, de hecho, a menudo límites bastante estrictos. Por otro lado, si un sistema puede proporcionar al menos una dirección de memoria (puntero) variable de tamaño de subproceso local, entonces esto permite el uso de bloques de memoria de tamaño arbitrario de una manera local de subproceso, asignando dicho bloque de memoria de forma dinámica y la dirección de memoria de ese bloque en la variable local de subproceso. En máquinas RISC , la convención de llamada a menudo reserva un registro de puntero de subproceso para este uso.
Implementación de Windows
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 la ranura TLS se considerará entonces "utilizado".
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
solo afecta a la variable del hilo actual. Se TlsFree
puede llamar a la función para liberar el índice de ranuras TLS .
Hay un bloque de información de subprocesos de 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 hilo tiene su propia copia de la tabla de almacenamiento local del hilo. Por lo tanto, cada hilo puede usar TlsSetValue (índice) de forma independiente y obtener el valor especificado a través de TlsGetValue (índice), 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 de ejecución. A diferencia de los valores de TlsXxx, estas páginas pueden contener direcciones válidas y arbitrarias. Sin embargo, estas direcciones son diferentes para cada subproceso en ejecución y, por lo tanto, no deben pasarse a funciones asíncronas (que pueden ejecutarse en un subproceso diferente) o pasarse al código que asume que una dirección virtual es única dentro de todo el proceso. Las secciones de TLS se administran mediante paginación de memoria y su tamaño se cuantifica a un tamaño de 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 tales secciones, porque no se inicializan correctamente cuando se cargan 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_create
y pthread_key_delete
se utilizan respectivamente para crear y eliminar una clave para datos específicos de subprocesos. 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 más tarde utilizando pthread_getspecific
.
Además, pthread_key_create
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 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).
Implementación específica del idioma
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).
C y C ++
En C11 , la palabra clave _Thread_local
se usa para definir variables locales de subprocesos. El encabezado
, si se admite, se define thread_local
como sinónimo de esa palabra clave. Uso de ejemplo:
#include thread_local int foo = 0 ;
C ++ 11 introduce la palabra clave thread_local
[2] que se puede utilizar en los siguientes casos
- Variables de nivel de espacio de nombres (globales)
- Archivo de variables estáticas
- Función variables estáticas
- Variables de miembros estáticos
Aparte de eso, varias implementaciones del compilador proporcionan formas específicas de declarar variables locales de subprocesos:
- Solaris Studio C / C ++, IBM XL C / C ++, [3] GNU C , [4] Clang [5] e Intel C ++ Compiler (sistemas Linux) [6] utilizan la sintaxis:
__thread int number;
- Visual C ++ , [7] Intel C / C ++ (sistemas Windows), [8] C ++ Builder y Digital Mars C ++ utilizan la sintaxis:
__declspec(thread) int number;
- C ++ Builder también admite la sintaxis:
int __thread number;
En las versiones de Windows anteriores a Vista y Server 2008, __declspec(thread)
funciona en archivos DLL solo cuando esos archivos DLL están vinculados al ejecutable y no funcionará para aquellos cargados con LoadLibrary () (puede ocurrir una falla de protección o daños en los datos). [9]
Common Lisp (y tal vez 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 Lisp que proporcionan subprocesos hacen esto. Common Lisp tiene numerosas variables dinámicas estándar, por lo que los subprocesos no se pueden agregar de manera sensata a una implementación del lenguaje sin que estas variables tengan semántica local de subprocesos en el 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 esta variable se anula, entonces 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 pueden ejecutarse al mismo tiempo en diferentes subprocesos, este enlace debe ser correctamente local de subprocesos, de lo contrario, cada subproceso peleará por quién controla una base de impresión global.
D
En la versión 2 de D , todas las variables estáticas y globales son subprocesos de forma predeterminada y se declaran con una sintaxis similar a las variables globales y estáticas "normales" en otros idiomas. Las variables globales deben solicitarse explícitamente mediante la palabra clave compartida :
int threadLocal ; // 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. [10] Para declarar una variable global "clásica" sin estas restricciones, se debe utilizar la palabra clave insegura __gshared : [11]
__gshared int global ; // Esta es una variable global simple y antigua.
Java
En Java , el objeto de ThreadLocal
clase implementa las variables locales de subproceso . ThreadLocal contiene una variable de tipo T, a la que se puede acceder mediante métodos get / set. Por ejemplo, la variable ThreadLocal que contiene un valor entero se ve así:
ThreadLocal final estático privado < 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 del subproceso de Java. En su lugar, cada objeto Thread almacena un mapa (no seguro para subprocesos) de objetos ThreadLocal a sus valores (a diferencia de que cada ThreadLocal tiene un mapa de objetos Thread a valores y genera una sobrecarga de rendimiento). [12]
Lenguajes .NET: C # y otros
En lenguajes de .NET Framework como C # , los campos estáticos se pueden marcar con el atributo ThreadStatic :
class FooBar { [ThreadStatic] private static int _foo ; }
En .NET 4.0, la clase System.Threading.ThreadLocal
class FooBar { sistema estático privado . Enhebrado . ThreadLocal < int > _foo ; }
También hay una API disponible para asignar dinámicamente variables locales de subprocesos.
Objeto Pascal
En Object Pascal (Delphi) o Free Pascal el threadvar palabra clave reservada se puede utilizar en lugar de 'var' para declarar variables utilizando el almacenamiento local de subprocesos.
var mydata_process : integer ; threadvar mydata_threadlocal : integer ;
C objetivo
En Cocoa , GNUstep y OpenStep , cada objeto NSThread tiene un diccionario local de subprocesos al que se puede acceder a través del método threadDictionary del subproceso .
NSMutableDictionary * dict = [[ NSThread currentThread ] threadDictionary ]; dict [ @ "A key" ] = @ "Algunos datos" ;
Perl
En Perl, los subprocesos se agregaron tarde en 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 toman por defecto su propio almacenamiento local para todas las variables, para minimizar el impacto de los subprocesos en el código existente que no es compatible con subprocesos. En Perl, se puede crear una variable de hilo compartido usando un atributo:
usar hilos ; usar hilos :: compartido ;my $ localvar ; my $ sharedvar : shared ;
PureBasic
En PureBasic, las variables de hilo se declaran con la palabra clave Threaded .
Var roscado
Pitón
En Python versión 2.4 o posterior, la clase local en el módulo de subprocesos se puede usar para crear almacenamiento local de subprocesos.
import threading mydata = threading . local () mydata . x = 1
Rubí
Ruby puede crear / acceder a variables locales de subprocesos usando métodos [] = / []:
Hilo . actual [ : user_id ] = 1
Oxido
Las variables locales de subprocesos se pueden crear en Rust utilizando la thread_local!
macro proporcionada por la biblioteca estándar de Rust:
use std :: cell :: RefCell ; use std :: hilo ; thread_local! ( FOO estático : RefCell < u32 > = RefCell :: new ( 1 )); FOO . con ( | f | { asert_eq! ( * f . pedir prestado (), 1 ); * f . pedir prestado () = 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 . con ( | f | { asert_eq! ( * f . pedir prestado (), 1 ); * f . pedir prestado () = 3 ; });});// esperar a que se complete el hilo y salir del pánico t . unirse (). desenvolver ();// el hilo original retiene el valor original de 2 a pesar de que el hilo secundario cambia el valor a 3 para ese hilo FOO . con ( | f | { asert_eq! ( * f . pedir prestado (), 2 ); });
Referencias
- ^ Pietrek, Matt (mayo de 2006). "Bajo el capó" . MSDN . Consultado el 6 de abril de 2010 .
- ^ Sección 3.7.2 en el estándar C ++ 11
- ^ IBM XL C / C ++: almacenamiento local de subprocesos
- ^ GCC 3.3.1: Almacenamiento local de subprocesos
- ^ Clang 2.0: notas de la versión
- ^ Notas de la versión del compilador Intel C ++ 8.1 (linux): almacenamiento local de subprocesos
- ^ Visual Studio 2003: modificador de clase de almacenamiento extendido de subprocesos
- ^ Intel C ++ Compiler 10.0 (windows): almacenamiento local de subprocesos
- ^ "Reglas y limitaciones de TLS"
- ^ Alexandrescu, Andrei (6 de julio de 2010). Capítulo 13 - Simultaneidad . El lenguaje de programación D . InformIT. pag. 3 . Consultado el 3 de enero de 2014 .
- ^ Bright, Walter (12 de mayo de 2009). "Migración a compartido" . dlang.org . Consultado el 3 de enero de 2014 .
- ^ "¿Cómo se implementa ThreadLocal de Java bajo el capó?" . Desbordamiento de pila . Stack Exchange . Consultado el 27 de diciembre de 2015 .
enlaces externos
- Compatibilidad con el procesador OpenMP Parallell para cierto hardware
- Shared_memory Acceso a las páginas de memoria y configuración de (cpu o kernel compatible, si es compatible)
- Context_switch También llamado cambio de tareas, los subprocesos, las páginas se aceleran por hardware o se proporcionan con el kernel
- Semaphore_ (programación) LOCK, si (cpu) no admite la memoria multipuerto lo suficiente (previene la congelación de la cpu)
- Manejo de ELF para almacenamiento local de subprocesos : documento sobre una implementación en C o C ++ .
- Referencia de plantilla de clase ACE_TSS
- Documentación de plantilla de clase RWTThreadLocal
- Artículo " Usar almacenamiento local de subprocesos para pasar datos específicos de subprocesos " por Doug Doedens
- " Almacenamiento local de subprocesos " por Lawrence Crowl
- Artículo " No siempre es agradable compartir " por Walter Bright
- Uso práctico de ThreadLocal en Java: http://www.captechconsulting.com/blogs/a-persistence-pattern-using-threadlocal-and-ejb-interceptors
- GCC " [1] "
- Óxido " [2] "