Szálspecifikus tároló

A Wikipédiából, a szabad enciklopédiából

A szálspecifikus tároló (Thread local strorage, TLS) egy konkurens programtervezési minta, ami lehetőséget biztosít arra, hogy egy adott szálra nézve lehessenek statikus vagy globális változóink, memóriaterületünk.

Ezt több helyen használják, ahol az egyszálú program globális változóit helyettesítik vele, amik egy többszálú alkalmazásban nem lennének megfelelőek. Például egy errno nevű globális változót használ a sok C függvény hibaállapot-tárolásra. Amennyiben ez egy darab globális változó lenne, az egyik szál által beleírt hibakódot egy másik szál felülírhatja még azelőtt, hogy az első szál által beállított hibakóddal bármilyen más kódrészlet is foglalkozott volna (tehát mielőtt lekezelte volna az adott hibát). Erre a problémára lehet megoldás jelen tervezési minta alkalmazása, amiben például a hibaállapotot jelző errno változó globálisnak néz ki a szálon belülről, viszont a valóságban ez egy adott szálra lokális.

Egy másik alkalmazási lehetősége az, amikor több szál gyűjt információt egyetlen globális változóba. Ahhoz, hogy elkerüljük a versenyhelyzetet (race condition), kölcsönös kizárást (mutual exclusion) kell alkalmaznunk. Ennek alternatívája lehet az, hogy minden szál rendelkezik egy adott szálra lokális változóval (defínció szerint ezeket a változókat másik szálból nem lehet sem olvasni, sem írni, tehát nem léphet fel versenyhelyzet), amiben az adatokat összegezheti, gyűjtheti. Ezt követően csak annyi a dolgunk, hogy a szálak által összegyűjtött információkat akkumuláljuk egy valóban globális változóba.

Sok rendszer korlátozza a szálbiztos tároló méretét, gyakran erősen.[1] Azonban, ha legalább egy mutatóra elegendő helyet biztosít a rendszer, azáltal tetszőleges méretű memóriablokkot használhatunk minden szál esetében, aminek a kezdőcímét tároljuk csak el a szálspecifikus tárolóban, így valójában ez sem feltétlen jelent komoly korlátozást.

Windows-implementáció[szerkesztés]

A TlsAlloc API függvény használható arra, hogy lefoglaljon egy TLS slot indexet, ami még nincs használatban. Ezután az index használttá válik.

A TlsGetValue és TlsSetValue függvények használhatók a TLS slot index által azonosított szállokális változó memóriahelyének írására és olvasására. A TlsSetValue csak az aktuális szál változójához fér hozzá. A TlsFree függvény felszabadítja a TLS slot indexet.

Minden szálban van egy Win32 Thread Information Block. Ennek egy bejegyzése a szállokális tár táblája. A TlsAlloc visszaad egy indexet ennek a táblának, ami minden hívás esetén más. Emiatt minden szál függetlenül hívhatja a TlsSetValue(index) függvényt, és a specifikált értékkel a TlsGetValue(index) függvényt, mivel ezek a szál saját szálspecifikus tárolójában állítanak be vagy keresnek meg egy értéket.

A TlsXxx függvényektől függetlenül a Windowson futó programok definiálhatnak egy szakaszt, amelyek futtatás alatt minden szálra más lapra képeződik le.A TlsXxx függvényektől eltérően bármely érvényes címet tartalmazhatnak. Ezek azonban szintén szálspecifikusak, ezért nem szabad őket átadni. A TLS szakaszokat memórialapozás kezeli, mérete a lap méretétől függ (x86-os gépeken 4 kB). Ilyeneket csak a fő végrehajtható definiálhat, DLL-ek nem, mivel nem inicializálhatók, amikor a LoadLibrary betölti őket.

Pthreads-implementáció[szerkesztés]

A Pthreads API-ban a szálra lokális memóriára mint szálspecifikus adatokra hivatkoznak.

Szálspecifikus adatok kulcsát létrehozni a pthread_key_create függvénnyel, törölni a pthread_key_delete függvénnyel lehet. Ezután a kulcs pthread_key_t néven tárolódik, ezt a többi szál is látja. A kulcshoz a specifikus adatot a pthread_setspecific társítja. Az adatokat később a pthread_getspecific függvény éri el.

A pthread_key_create elfogad destruktort paraméterként, ez meghívódik a szál kilépésekor, ha az adat nem NULL. A destruktor paraméterként megkapja a kulcs által hivatkozott objektumot, ezért azt el tudja takarítani, a hálózati kapcsolatokat lezárni és az erőforrásokat elengedni. A program kilépéskor meghívja a pthread_key_delete függvényt, ami folyamat szinten felszabadít mindent.

Unix rendszereken nem a Pthreads az egyetlen API, ami meg tudja osztani a memóriát. Egy régebbi, de még mindig érvényes szabvány a MIT-SHM. Maga a pthreads is számos inkompatibilis változáson van túl, a Linux libc már nem használja a /lib/tls/libs használatát, továbbá a pthreads már nem "tls", és több helyen is eltér annak szabványától. Például Linux rendszereken a gas(1) és ld(1) folyamatosan módosították a pthreads implementációját fordítási időben. A tls fontos a libc hatékonysága szempontjából, amikor többszálú alkalmazásokat linkel össze. Viszont használata nem ajánlott a kód gyors avulása miatt, és mivel nem operációs rendszer könyvtár.

Nyelvspecifikus megoldások[szerkesztés]

Különböző programozási nyelvek további támogatást nyújtanak a szálspecifikus tárolóhoz.

C és C++[szerkesztés]

2011 előtt a C és a C++ nyelvekben nem volt további lehetőség beépítve, az operációs rendszer API-ját és más programkönyvtárakat lehetett használni, például a Boost szálkezelését.

C11-ben a szállokális változók a _Thread_local kulcsszóval definiálhatók. A <threads.h> szinonimaként bevezeti a thread_local kulcsszót, ha támogatva van. Példa:

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

A C++11 a thread_local kulcsszót vezeti be,[2] ami a következő esetekben használható:

  • Névtér szintű (globális) változók
  • Fájl szintű statikus változók
  • Függvény szintű statikus változók
  • Statikus tagváltozók

Különböző fordítók specifikus módszereket nyújtanak a szállokális változók definiálásához:

A Vista és a Server 2008 előtti Windowsokban a __declspec(thread) csak akkor működött DLL-ekben, ha azok végrehajthatókhoz voltak kötve, a LoadLibrary() által betöltötteknél nem. Védelmi hiba vagy adatvesztés előfordulhat.[10]

Common Lisp[szerkesztés]

A Common Lisp (esetleg más Lisp dialektusok) lehetővé teszik dinamikus hatókörű változók definiálását. Ezeknek privát kapcsolatuk, kötésük van egy függvényhez. Ez az absztrakció természetszerűleg szálspecifikus tárolóra képeződik le, és a Lisp szálak alkalmasak is arra, hogy ezt felhasználják.

A Common Lispnek számos standard dinamikus változója van. A nyelv egy implementációjának nem szabad ezeket figyelmen kívül hagynia. Szállokális szemantikával kell ezeket megvalósítani.

Például a *print-base* standard változó meghatározza az alapértelmezett számrendszert (tulajdonképpen az alapot), amiben az egészeket ki lehet írni. Ha ezt felülírják, akkor minden kód ebben a számrendszerben fog kiírni.

;;; function foo and its children will print
;; in hexadecimal:
(let ((*print-base* 16)) (foo))

Ha a függvényeket a konkurrens szálak párhuzamosan hívják, ennek a kötésnek valóban szállokálisnak kell lennie, különben a szálaknak várniuk kell egymásra, hogy megszerezzék az alapot.

D[szerkesztés]

A D 2. verziójában a static és a globális változók alapértelmezetten szállokálisak. Deklarációjuk megegyezik más nyelvek normál static és globális váltóinak deklarációjával. Globális változók a shared kulcsszóval deklarálhatók.

int threadLocal;  // This is a thread-local variable.
shared int global;  // This is a global variable shared with all threads.

Ez tárolási osztályként is működik, a shared változóknak különféle korlátozásokat kell betartani, amelyek statikusan biztosítják az adat integritást.[11] Klasszikus globális változók a __gshared kulcsszóval deklarálhatók:[12]

__gshared int global;  // This is a plain old global variable.

Java[szerkesztés]

Java nyelven a szállokális változókat a ThreadLocal osztályobjektum valósítja meg. Ez generikus, az általa tárolt objektum vagy érték get/set metódusokkal érhető el. Például, ha a ThreadLocal Integer objektumot kezel:

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

Az Oracle/OpenJDK nem a natív szállokális tárat használja, habár más tekintetben az operációs rendszer szálaira hagyatkozik. Ehelyett a Thread objektumok tárolnak egy nem szálbiztos megfeleltetést a ThreadLocal objektumok és a bennük tárolt érték között.[13]

.NET nyelvek[szerkesztés]

A .NET keretrendszerben az osztályobjektumok megjelölhetők a ThreadStatic attributummal:[14]

class FooBar {
   [ThreadStatic] static int foo;
}

A .NET 4.0 rendszerben a System.Threading.ThreadLocal<T> elérhető szállokális változók allokálására és lusta betöltésére:[15]

class FooBar {
   private static System.Threading.ThreadLocal<int> foo;
}

Továbbá elérhető egy API a szállokális változók dinamikus allokálásához.[16]

Object Pascal[szerkesztés]

Az Object Pascal (Delphi) vagy a Free Pascal nyelvekben a threadvar fenntartott kulcsszóval szállokális változók definiálhatók.

var
   mydata_process: integer;
threadvar
   mydata_threadlocal: integer;

Objective-C[szerkesztés]

A Cocoa, GNUstep, és OpenStep API-kban minden NSThread objektumnak van szállokális könyvtára, ami hozzáférhető a szál threadDictionary metódusával.

NSMutableDictionary *dict = [[NSThread currentThread] threadDictionary];
dict[@"A key"] = @"Some data";

Perl[szerkesztés]

A Perl nyelvhez utólag adták hozzá a szálakat, amikor a Comprehensive Perl Archive Network (CPAN) már bőven tartalmazott kódot. A szálak alapértelmezetten szállokális változókat használnak, így minimalizálják a szálak hatását a nem szálbiztosnak készült kódra. Ha megosztott változó kell, azt attributummal kell ellátni:

use threads;
use threads::shared;

my $localvar;
my $sharedvar :shared;

Python[szerkesztés]

Python 2.4-től a threading modul local osztálya használható szállokális tároló létrehozására.

import threading
mydata = threading.local()
mydata.x = 1

Ruby[szerkesztés]

Rubyban szállokális változók a []=/[] metódusokkal hozhatók létre és férhetők hozzá.

Thread.current[:user_id] = 1

Jegyzetek[szerkesztés]

  1. Thread Local Storage (Windows) (angol nyelven). msdn.microsoft.com. (Hozzáférés: 2017. december 12.)
  2. Section 3.7.2 in C++11 standard
  3. IBM XL C/C++: Thread-local storage
  4. GCC 3.3.1: Thread-Local Storage
  5. Clang 2.0: release notes
  6. Intel C++ Compiler 8.1 (linux) release notes: Thread-local Storage
  7. Visual Studio 2003:
  8. Thread extended storage-class modifier
  9. Intel C++ Compiler 10.0 (windows): Thread-local storage
  10. "Rules and Limitations for TLS"
  11. Alexandrescu, Andrei: Chapter 13 - Concurrency. The D Programming Language pp. 3. InformIT, 2010. július 6. (Hozzáférés: 2014. január 3.)
  12. Bright, Walter: Migrating to Shared. dlang.org, 2009. május 12. (Hozzáférés: 2014. január 3.)
  13. How is Java's ThreadLocal implemented under the hood?. Stack Overflow . Stack Exchange. (Hozzáférés: 2015. december 27.)
  14. ThreadStatic attribute
  15. System.Threading.ThreadLocal<T>
  16. an API

Fordítás[szerkesztés]

Ez a szócikk részben vagy egészben a Thread-local storage című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.