Pamięć lokalna wątku — Thread-local storage

Magazyn lokalny wątku ( TLS ) to metoda programowania komputerowego, która używa statycznej lub globalnej pamięci lokalnej dla wątku .

Chociaż stosowanie zmiennych globalnych jest ogólnie zniechęca w nowoczesnym programowaniu, starsze systemy operacyjne takie jak UNIX są przeznaczone dla sprzętu jednoprocesorowym i wymaga pewnego dodatkowego mechanizmu, aby zachować semantykę przed- wklęsłych API. Przykładem takich sytuacji jest sytuacja, w której funkcje używają zmiennej globalnej do ustawienia warunku błędu (na przykład zmiennej globalnej errnoużywanej przez wiele funkcji biblioteki C). Gdyby errnobyła zmienną globalną, wywołanie funkcji systemowej w jednym wątku może nadpisać wartość wcześniej ustawioną przez wywołanie funkcji systemowej w innym wątku, prawdopodobnie przed wykonaniem kodu w tym innym wątku, który mógłby sprawdzić warunek błędu. Rozwiązaniem jest posiadanie errnozmiennej, która wygląda tak, jakby była globalna, ale w rzeczywistości istnieje raz na wątek — tj. żyje w pamięci lokalnej wątku. Drugim przypadkiem użycia byłoby wiele wątków gromadzących informacje w zmiennej globalnej. Aby uniknąć sytuacji wyścigu , każdy dostęp do tej zmiennej globalnej musiałby być chroniony przez muteks . Alternatywnie każdy wątek może akumulować się w zmiennej lokalnej wątku (która z definicji nie może być odczytywana ani zapisywana z innych wątków, co oznacza, że ​​nie może być warunków wyścigu). Następnie wątki muszą tylko zsynchronizować ostateczną akumulację z własnej zmiennej lokalnej wątku w jedną, prawdziwie globalną zmienną.

Wiele systemów nakłada ograniczenia na rozmiar bloku pamięci lokalnej w wątku, w rzeczywistości często są to raczej wąskie limity. Z drugiej strony, jeśli system może zapewnić co najmniej adres pamięci (wskaźnik) o zmiennej wielkości lokalnej wątku, to pozwala to na użycie bloków pamięci o dowolnej wielkości w sposób lokalny wątku, poprzez dynamiczne przydzielanie takiego bloku pamięci i przechowywanie adres pamięci tego bloku w zmiennej lokalnej wątku. Na maszynach RISC konwencja wywoływania często rezerwuje do tego celu rejestr wskaźnika wątku .

Implementacja Windows Windows

Interfejs programowania aplikacji funkcji (API), TlsAllocmogą być wykorzystane do uzyskania w wolne indeks szczelinowy TLS ; Indeks TLS gniazdo zostanie uznane za „używane”.

Funkcje TlsGetValuei TlsSetValuesą następnie używane do odczytu i zapisu adresu pamięci do zmiennej lokalnej wątku identyfikowanej przez indeks gniazda TLS . TlsSetValuewpływa tylko na zmienną dla bieżącego wątku. TlsFreeFunkcja może być wywołana w celu uwolnienia indeks TLS gniazda .

Dla każdego wątku istnieje blok informacji o wątku Win32 . Jeden z wpisów w tym bloku to tabela pamięci lokalnej dla tego wątku. TlsAlloc zwraca indeks do tej tabeli, unikalny dla przestrzeni adresowej, dla każdego wywołania. Każdy wątek ma własną kopię tabeli pamięci lokalnej wątku. W związku z tym każdy wątek może niezależnie używać TlsSetValue(index) i uzyskać określoną wartość za pośrednictwem TlsGetValue(index), ponieważ te ustawiają i wyszukują wpis we własnej tabeli wątku.

Oprócz rodziny funkcji TlsXxx, pliki wykonywalne Windows mogą definiować sekcję, która jest mapowana na inną stronę dla każdego wątku procesu wykonywania. W przeciwieństwie do wartości TlsXxx, te strony mogą zawierać dowolne i prawidłowe adresy. Adresy te są jednak różne dla każdego wykonującego wątku i dlatego nie powinny być przekazywane do funkcji asynchronicznych (które mogą być wykonywane w innym wątku) ani w inny sposób przekazywane do kodu, który zakłada, że ​​adres wirtualny jest unikalny w całym procesie. Sekcje TLS są zarządzane za pomocą stronicowania pamięci, a ich rozmiar jest kwantowany do rozmiaru strony (4kB na maszynach x86). Takie sekcje mogą być definiowane tylko w głównym pliku wykonywalnym programu - biblioteki DLL nie powinny zawierać takich sekcji, ponieważ nie są one poprawnie inicjowane podczas ładowania z LoadLibrary.

Implementacja wątków

W interfejsie API Pthreads pamięć lokalna dla wątku jest określana terminem dane specyficzne dla wątku.

Funkcje pthread_key_createi pthread_key_deletesłużą odpowiednio do tworzenia i usuwania klucza dla danych specyficznych dla wątku. Typ klucza jest jawnie pozostawiony jako nieprzezroczysty i jest określany jako pthread_key_t. Ten klucz jest widoczny dla wszystkich wątków. W każdym wątku klucz może być powiązany z danymi specyficznymi dla wątku poprzez pthread_setspecific. Dane można później odzyskać za pomocą pthread_getspecific.

Dodatkowo pthread_key_createmoże opcjonalnie zaakceptować funkcję destruktora, która będzie automatycznie wywoływana przy wyjściu z wątku, jeśli dane specyficzne dla wątku nie są NULL . Destruktor otrzymuje wartość skojarzoną z kluczem jako parametr, dzięki czemu może wykonywać akcje czyszczące (zamykanie połączeń, zwalnianie pamięci itp.). Nawet jeśli określono destruktor, program nadal musi wywołać, pthread_key_deleteaby zwolnić dane specyficzne dla wątku na poziomie procesu (destruktor zwalnia tylko dane lokalne dla wątku).

Implementacja specyficzna dla języka

Oprócz polegania na programistach, którzy wywołują odpowiednie funkcje API, możliwe jest również rozszerzenie języka programowania o obsługę lokalnego przechowywania wątków (TLS).

C i C++

W C11 słowo kluczowe _Thread_localsłuży do definiowania zmiennych lokalnych wątku. Nagłówek <threads.h>, jeśli jest obsługiwany, thread_localjest synonimem tego słowa kluczowego. Przykładowe użycie:

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

C ++ 11 wprowadza thread_localsłowo kluczowe, które może być użyte w następujących przypadkach

  • Zmienne poziomu przestrzeni nazw (globalne)
  • Zmienne statyczne pliku
  • Funkcje statyczne zmienne
  • Statyczne zmienne składowe

Poza tym różne implementacje kompilatorów zapewniają określone sposoby deklarowania zmiennych lokalnych wątków:

  • Solaris Studio C/C++, IBM XL C/C++, GNU C , Clang i Intel C++ Compiler (systemy Linux) używają składni:
    __thread int number;
  • Visual C ++ , Intel C / C ++ (systemy Windows), C ++ Builder i Digital Mars C ++ używają składni:
    __declspec(thread) int number;
  • C++Builder obsługuje również składnię:
    int __thread number;

W wersjach Windows wcześniejszych niż Vista i Server 2008 __declspec(thread)działa w bibliotekach DLL tylko wtedy, gdy te biblioteki DLL są powiązane z plikiem wykonywalnym i nie będą działać w przypadku tych załadowanych funkcją LoadLibrary() (może wystąpić błąd ochrony lub uszkodzenie danych).

Common Lisp (i może inne dialekty)

Common Lisp udostępnia funkcję zwaną zmiennymi o dynamicznym zakresie .

Zmienne dynamiczne mają powiązanie, które jest prywatne dla wywołania funkcji i wszystkich dzieci wywoływanych przez tę funkcję.

Ta abstrakcja w naturalny sposób mapuje do pamięci specyficznej dla wątków, a implementacje Lisp, które zapewniają wątki, robią to. Common Lisp ma wiele standardowych zmiennych dynamicznych, dlatego wątki nie mogą być rozsądnie dodawane do implementacji języka bez tych zmiennych, które mają semantykę lokalną wątku w dynamicznym wiązaniu.

Na przykład zmienna standardowa *print-base*określa domyślną podstawę, w której wypisywane są liczby całkowite. Jeśli ta zmienna jest nadpisana, to cały kod obejmujący wypisze liczby całkowite w alternatywnej podstawie:

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

Jeśli funkcje mogą być wykonywane jednocześnie w różnych wątkach, to powiązanie musi być poprawnie lokalne dla wątku, w przeciwnym razie każdy wątek będzie walczył o to, kto kontroluje globalną podstawę drukowania.

re

W wersji D 2 wszystkie zmienne statyczne i globalne są domyślnie lokalne dla wątków i są deklarowane ze składnią podobną do „normalnych” zmiennych globalnych i statycznych w innych językach. Zmienne globalne należy jawnie zażądać za pomocą udostępnionego słowa kluczowego:

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

Wspólne słowa kluczowego działa zarówno jako klasa przechowywania, a jako typ kwalifikatora - współdzielone zmienne podlegają pewnym ograniczeniom, które statycznie wymuszają integralność danych. Aby zadeklarować „klasyczną” zmienną globalną bez tych ograniczeń, należy użyć niebezpiecznego słowa kluczowego __gshared :

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

Jawa

W Javie zmienne lokalne wątku są implementowane przez obiekt ThreadLocal klasy . ThreadLocal przechowuje zmienną typu T, która jest dostępna za pośrednictwem metod get/set. Na przykład zmienna ThreadLocal zawierająca wartość Integer wygląda tak:

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

Przynajmniej w przypadku Oracle/OpenJDK nie używa to natywnej pamięci lokalnej wątków, mimo że wątki systemu operacyjnego są używane do innych aspektów wątków w Javie. Zamiast tego każdy obiekt Thread przechowuje mapę (niebezpieczną dla wątków) obiektów ThreadLocal na ich wartości (w przeciwieństwie do każdego obiektu ThreadLocal posiadającego mapę obiektów ThreadLocal do wartości i ponoszącego obciążenie wydajności).

Języki .NET: C# i inne

W językach .NET Framework , takich jak C # , pola statyczne można oznaczać atrybutem ThreadStatic :

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

W .NET 4.0 klasa System.Threading.ThreadLocal<T> jest dostępna do przydzielania i leniwego ładowania zmiennych lokalnych wątków.

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

Dostępny jest również interfejs API do dynamicznego przydzielania zmiennych lokalnych wątków.

Obiekt Pascal

W Object Pascal (Delphi) lub Free Pascal threadvar zastrzeżone słowo kluczowe może być stosowany zamiast „var” zadeklarować zmienne używając pamięć lokalna wątku.

var
   mydata_process: integer;
threadvar
   mydata_threadlocal: integer;

Cel C

W Cocoa , GNUstep i OpenStep , każdy obiekt NSThread ma słownik lokalny wątku, do którego można uzyskać dostęp za pomocą metody threadDictionary wątku .

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

Perl

W Perlu wątki zostały dodane późno w ewolucji języka, po tym, jak duża część istniejącego kodu była już obecna w Comprehensive Perl Archive Network (CPAN). W ten sposób wątki w Perlu domyślnie pobierają własną pamięć lokalną dla wszystkich zmiennych, aby zminimalizować wpływ wątków na istniejący kod, który nie rozpoznaje wątków. W Perlu zmienna współdzielona przez wątki może być utworzona za pomocą atrybutu:

use threads;
use threads::shared;

my $localvar;
my $sharedvar :shared;

PureBasic

W PureBasic zmienne wątków są deklarowane za pomocą słowa kluczowego Threaded .

Threaded Var

Pyton

W Pythonie w wersji 2.4 lub nowszej lokalna klasa w module wątków może być użyta do tworzenia lokalnego magazynu wątków.

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

Rubin

Ruby może tworzyć / uzyskiwać dostęp do zmiennych lokalnych wątku przy użyciu metod [] = / []:

Thread.current[:user_id] = 1

Rdza

Zmienne lokalne wątku mogą być tworzone w Ruście za pomocą thread_local!makra dostarczanego przez standardową bibliotekę Rusta:

use std::cell::RefCell;
use std::thread;

thread_local!(static FOO: RefCell<u32> = RefCell::new(1));

FOO.with(|f| {
    assert_eq!(*f.borrow(), 1);
    *f.borrow_mut() = 2;
});

// each thread starts out with the initial value of 1, even though this thread already changed its copy of the thread local value to 2
let t = thread::spawn(move|| {
    FOO.with(|f| {
        assert_eq!(*f.borrow(), 1);
        *f.borrow_mut() = 3;
    });
});

// wait for the thread to complete and bail out on panic
t.join().unwrap();

// original thread retains the original value of 2 despite the child thread changing the value to 3 for that thread
FOO.with(|f| {
    assert_eq!(*f.borrow(), 2);
});

Bibliografia

Linki zewnętrzne