Articulo de referencia

Almacenamiento local de subprocesos

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 a un subproceso . Est...

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 a un subproceso . Este concepto permite almacenar datos que parecen ser globales en un sistema con subprocesos independientes.

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

Uso

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

Un segundo caso de uso sería el de múltiples hilos acumulando 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 hilo podría acumular la información en una variable local, eliminando así cualquier posibilidad de condición de carrera y, por lo tanto, la necesidad de bloqueo. Los hilos solo tendrían que sincronizar la información acumulada desde su propia variable local a una única variable global.

Implementación de Windows

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

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

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

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. Sin embargo, estas direcciones son diferentes para cada hilo en ejecución y, por lo tanto, no deben pasarse a funciones asíncronas (que pueden ejecutarse en un hilo diferente) ni a código que asuma que una dirección virtual es única dentro de todo el proceso. Las secciones TLS se gestionan 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 pueden definirse dentro del ejecutable principal de un programa; las DLL no deben contenerlas, ya que no se inicializan correctamente al cargarse con LoadLibrary.

Implementación de Pthreads

En la API de Pthreads , la memoria local a 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 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 puede asociarse con datos específicos del hilo mediante pthread_setspecific. Los datos pueden recuperarse posteriormente utilizando pthread_getspecific.

Además, pthread_key_createopcionalmente puede aceptar una función destructora que se llamará automáticamente al finalizar el hilo, si los datos específicos del hilo no son nulos . 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 debe seguir llamando pthread_key_deletea la función para 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 depender de que los programadores llamen a las funciones API adecuadas, también es posible extender el lenguaje de programación para que admita el almacenamiento local de subprocesos (TLS).

C y C++

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

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

hilo_local int foo = 0 ;

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

Además de eso, varias implementaciones de compiladores proporcionan formas específicas de declarar variables locales de subprocesos:

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

Lisp común y otros dialectos

Common Lisp ofrece una característica llamada variables con ámbito dinámico .

Las variables dinámicas tienen una vinculación que es privada para la invocación de una función y todas las funciones hijas llamadas por esa función.

Esta abstracción se corresponde naturalmente con el almacenamiento específico de cada hilo, y las implementaciones de Lisp que admiten hilos lo hacen. Common Lisp cuenta con numerosas variables dinámicas estándar, por lo que no es conveniente añadir hilos a una implementación del lenguaje sin que estas variables tengan una semántica local de hilo en el enlace dinámico.

Por ejemplo, la variable estándar *print-base*determina la base numérica predeterminada en la que se imprimen los números enteros. Si se sobrescribe esta variable, hasta que finalice la sobrescritura, el código imprimirá los números enteros en una base numérica alternativa:

;;; 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 a cada subproceso; de lo contrario, cada subproceso competirá por controlar una base de impresión global.

D

En D versión 2, todas las variables estáticas y globales son locales a un hilo por defecto y se declaran con una sintaxis similar a la de las variables globales y estáticas "normales" en otros lenguajes. Las variables globales deben solicitarse explícitamente utilizando 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 tanto como clase de almacenamiento como calificador de tipo : las variables compartidas están sujetas a ciertas restricciones que imponen estáticamente la integridad de los datos. [ 13 ] Para declarar una variable global "clásica" sin estas restricciones, se debe usar la palabra clave unsafe __gshared : [ 14 ]

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

Java

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

private static final ThreadLocal < Integer > myThreadLocalInteger = new ThreadLocal < Integer > ();

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

Lenguajes .NET: C# y otros

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

clase FooBar { [ThreadStatic] private static int _foo ; }

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

clase FooBar { private static System . Threading . ThreadLocal < int > _foo ; }

También está disponible una API para la asignación dinámica de variables locales de subprocesos. [ 17 ] : 899–890

Objeto Pascal

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

var mydata_process : integer ; threadvar mydata_threadlocal : integer ;

Objetivo-C

En Cocoa , GNUstep y OpenStep , cada NSThreadobjeto tiene un diccionario local de 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 hilos se añadieron tardíamente en la evolución del lenguaje, después de que ya existiera una gran cantidad de código en la Red Integral de Archivos de Perl (CPAN). Por lo tanto, en Perl, los hilos utilizan por defecto su propio almacenamiento local para todas las variables, con el fin de minimizar el impacto de los hilos en el código existente que no los admite. En Perl, se puede crear una variable compartida entre hilos utilizando un atributo:

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

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 del módulo threading se puede utilizar para crear almacenamiento local para cada hilo.

import threading mydata = threading.local ( ) mydata.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 de hilo utilizando los métodos []=/ []:

Hilo.actual [ : user_id ] = 1

Óxido

En Rust , las variables locales de hilo se pueden crear utilizando la thread_local!macro proporcionada por la biblioteca estándar de Rust:

use std :: cell :: RefCell ; use std :: thread ;thread_local! ( static 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 . borrow (), 1 ); * f . borrow_mut () = 3 ; }); });// esperar a que el hilo se complete y salir en caso de pánico t . join (). unwrap ();// El hilo original conserva el valor original de 2 a pesar de que el hilo hijo cambia el valor a 3 para ese hilo FOO . with ( | f | { assert_eq! ( * f . borrow (), 2 ); });

Véase también

  • OpenMP : otra herramienta de multiprocesamiento de memoria compartida que admite almacenamiento por hilo a través de "cláusulas de atributos de compartición de datos" (véase en §Cláusulas ).

Referencias

  1. Pietrek, Matt (mayo de 2006). "Bajo el capó" . Microsoft Systems Journal . 11 (5). Archivado del original el 9 de septiembre de 2010. Recuperado el 6 de abril de 2010 .
  2. "Biblioteca de soporte de concurrencia - cppreference.com" . en.cppreference.com .
  3. Sección 3.7.2 en el estándar C++11
  4. "Información del compilador 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 del 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 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 que ofrece Clang. Además de las extensiones aquí enumeradas, Clang pretende ser compatible 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 del original (PDF) el 19 de enero de 2015.
  10. 1 2 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" . Hilo.
  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). "Migrando a Shared" . dlang.org . Consultado el 3 de enero de 2014 .
  15. 1 2 Bloch 2018 , págs. 151-155, §Punto 33: Considerar contenedores heterogéneos con tipado seguro.
  16. "¿Cómo se implementa internamente ThreadLocal de Java?" . Stack Overflow . Stack Exchange . Consultado el 27 de diciembre de 2015 .
  17. 1 2 3 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). C# 10 en pocas palabras (Primera  ed.). O'Reilly. ISBN 978-1-098-12195-2.
  • Bloch, Joshua (2018). «Java eficaz: Guía del lenguaje de programación» (tercera  ed.). 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< TIPO >
  • Documentación de la plantilla de clase RWTThreadLocal<Type>
  • Artículo " Utilice el almacenamiento local del hilo para pasar datos específicos del hilo " por Doug Doedens
  • " Almacenamiento local de subprocesos " por Lawrence Crowl
  • Artículo " No siempre es agradable compartir " de Walter Bright
  • Uso práctico de ThreadLocal en Java: http://www.captechconsulting.com/blogs/a-persistence-pattern-using-threadlocal-and-ejb-interceptors
  • GCC ""
  • Óxido ""