Локальное хранилище потока

редактировать

Локальное хранилище потока (TLS ) - это метод компьютерного программирования, который использует статическую или глобальную память, локальную для поток.

Хотя использование глобальных переменных обычно не рекомендуется в современном программировании, устаревшие операционные системы, такие как UNIX, разработаны для однопроцессорного оборудования и требуют некоторого дополнительного механизма для сохранения семантики. предварительно повторно входимых API. Примером таких ситуаций является ситуация, когда функции используют глобальную переменную для установки условия ошибки (например, глобальная переменная errno , используемая многими функциями библиотеки C). Если errnoбыла глобальной переменной, вызов системной функции в одном потоке может перезаписать значение, ранее установленное вызовом системной функции в другом потоке, возможно, до того, как следующий код в этом другом потоке сможет проверить для состояния ошибки. Решение состоит в том, чтобы errnoбыла переменной, которая выглядит как глобальная, но на самом деле существует один раз для каждого потока, т. Е. Находится в локальном хранилище потока. Второй вариант использования - несколько потоков, накапливающих информацию в глобальной переменной. Чтобы избежать состояния гонки, каждый доступ к этой глобальной переменной должен быть защищен мьютексом . В качестве альтернативы каждый поток может накапливаться в локальной переменной потока (которая, по определению, не может быть прочитана или записана из других потоков, что означает, что не может быть условий гонки). Затем потокам нужно только синхронизировать окончательное накопление из своей собственной локальной переменной потока в единственную, действительно глобальную переменную.

Многие системы накладывают ограничения на размер блока локальной памяти потока, на самом деле часто довольно жесткие ограничения. С другой стороны, если система может предоставить, по крайней мере, адрес памяти (указатель) с переменной размером с локальный поток, то это позволяет использовать блоки памяти произвольного размера локально по потоку, выделяя такие блок памяти динамически и сохранение адреса памяти этого блока в локальной переменной потока. На RISC-машинах соглашение о вызовах часто резервирует регистр указателя потока для этого использования.

Содержание
  • 1 Реализация Windows
  • 2 Реализация Pthreads
  • 3 Реализация для конкретного языка
    • 3.1 C и C ++
    • 3.2 Common Lisp (и, возможно, другие диалекты)
    • 3.3 D
    • 3.4 Java
    • 3.5..NET языки: C # и другие
    • 3.6 Object Pascal
    • 3.7 Objective-C
    • 3.8 Perl
    • 3.9 PureBasic
    • 3.10 Python
    • 3.11 Ruby
    • 3.12 Rust
  • 4 Ссылки
  • 5 Внешние ссылки
Реализация Windows

Интерфейс прикладного программирования (API), функция TlsAllocможет использоваться для получения индекс неиспользуемого слота TLS; тогда индекс слота TLS будет считаться «использованным».

Функции TlsGetValueи TlsSetValueзатем используются для чтения и записи адреса памяти в локальную переменную потока, идентифицированную индексом слота TLS. TlsSetValueвлияет только на переменную для текущего потока. Функция TlsFreeможет быть вызвана для освобождения индекса слота TLS.

Для каждого потока существует блок информации о потоке Win32. Одна из записей в этом блоке - это локальная таблица хранения потока для этого потока. TlsAlloc возвращает индекс этой таблицы, уникальный для каждого адресного пространства, для каждого вызова. Каждый поток имеет свою собственную копию таблицы локального хранилища потока. Следовательно, каждый поток может независимо использовать TlsSetValue (index) и получать указанное значение через TlsGetValue (index), потому что они устанавливают и ищут запись в собственной таблице потока.

Помимо семейства функций TlsXxx, исполняемые файлы Windows могут определять раздел, который отображается на другую страницу для каждого потока исполняемого процесса. В отличие от значений TlsXxx, эти страницы могут содержать произвольные и действительные адреса. Эти адреса, однако, различны для каждого исполняемого потока и поэтому не должны передаваться асинхронным функциям (которые могут выполняться в другом потоке) или иным образом передаваться коду, который предполагает, что виртуальный адрес уникален в рамках всего процесса. Разделы TLS управляются с помощью подкачки памяти, и ее размер квантуется по размеру страницы (4 КБ на машинах x86). Такие разделы могут быть определены только внутри основного исполняемого файла программы - DLL не должны содержать такие разделы, потому что они неправильно инициализируются при загрузке с LoadLibrary.

Реализация Pthreads

В API Pthreads локальная для потока память обозначается термином «Данные, зависящие от потока».

Функции pthread_key_createи pthread_key_deleteиспользуются соответственно для создания и удаления ключа для данных, зависящих от потока. Тип ключа явно оставлен непрозрачным и обозначается как pthread_key_t. Этот ключ виден всем потокам. В каждом потоке ключ может быть связан с данными потока через pthread_setspecific. Данные могут быть позже получены с помощью pthread_getspecific.

. Кроме того, pthread_key_createможет дополнительно принимать функцию деструктора, которая будет автоматически вызываться при выходе из потока, если специфичные для потока данные не равны NULL. Деструктор получает значение, связанное с ключом, в качестве параметра, чтобы он мог выполнять действия по очистке (закрытие соединений, освобождение памяти и т. Д.). Даже если деструктор указан, программа все равно должна вызвать pthread_key_delete, чтобы освободить данные, зависящие от потока, на уровне процесса (деструктор освобождает только данные, локальные для потока).

Реализация для конкретного языка

Помимо того, что программисты будут вызывать соответствующие функции API, можно также расширить язык программирования для поддержки локального хранилища потоков (TLS).

C и C ++

В C11 ключевое слово _Thread_localиспользуется для определения локальных переменных потока. Заголовок , если он поддерживается, определяет thread_localкак синоним этого ключевого слова. Пример использования:

#include thread_local int foo = 0;

C ++ 11 вводит ключевое слово thread_local, которое может использоваться в следующих случаях

  • переменные уровня пространства имен (глобальные)
  • Статические переменные файла
  • Статические переменные функции
  • Статические переменные-члены

Помимо этого, различные реализации компилятора предоставляют определенные способы объявления локальных переменных потока:

  • Solaris Studio C / C ++, IBM XL C / C ++, GNU C, Clang и Intel C ++ Compiler (системы Linux) используют синтаксис:
    __thread int number;
  • Visual C ++, Intel C / C ++ (системы Windows), C ++ Builder и Digital Mars C ++ используют синтаксис:
    __declspec (thread) int number;
  • C ++ Builder также поддерживает синтаксис:
    int __thread number;

В версиях Windows до Vista и Server 2008 __declspec (thread)работает в библиотеках DLL только в том случае, если эти библиотеки DLL привязаны к исполняемому файлу, и не будет работать для них загружен с помощью LoadLibrary () (может произойти сбой защиты или повреждение данных).

Common Lisp (и, возможно, другие диалекты)

Common Lisp предоставляет функцию, называемую переменными с динамической областью действия.

Динамические переменные имеют привязку, которая является частной для вызова функции и всех дочерних элементов, вызываемых этой функцией.

Эта абстракция естественным образом отображается в хранилище, зависящее от потока, и реализации Lisp, которые предоставляют потоки, делают это. Common Lisp имеет множество стандартных динамических переменных, поэтому потоки не могут быть разумно добавлены к реализации языка без этих переменных, имеющих локальную для потока семантику в динамическом связывании.

Например, стандартная переменная * print-base *определяет систему счисления по умолчанию, в которой печатаются целые числа. Если эта переменная переопределена, то весь включающий код будет печатать целые числа в альтернативной системе счисления:

;;; функция foo и ее дочерние элементы будут печатать ;; в шестнадцатеричном формате: (let ((* print-base * 16)) (foo))

Если функции могут выполняться одновременно в разных потоках, эта привязка должна быть правильно локальна для потока, иначе каждый поток будет бороться за то, кто контролирует глобальная основа печати.

D

В D версии 2 все статические и глобальные переменные по умолчанию являются локальными для потока и объявляются с синтаксисом, аналогичным «нормальным» глобальным и статическим переменным в других языках. Глобальные переменные должны быть явно запрошены с использованием общего ключевого слова:

int threadLocal; // Это локальная переменная потока. общий int global; // Это глобальная переменная, совместно используемая всеми потоками.

Ключевое слово shared работает и как класс хранилища, и как квалификатор типа . - общие переменные подвергаются некоторым ограничениям, которые статически обеспечивают целостность данных. Чтобы объявить «классическую» глобальную переменную без этих ограничений, необходимо использовать ключевое слово unsafe __gshared:

__gshared int global; // Это простая старая глобальная переменная.

Java

В Java локальные переменные потока реализуются с помощью объекта ThreadLocal class. ThreadLocal содержит переменную типа T, доступную через методы get / set. Например, переменная ThreadLocal, содержащая целочисленное значение, выглядит следующим образом:

private static final ThreadLocal myThreadLocalInteger = new ThreadLocal ();

По крайней мере для Oracle / OpenJDK, это не использует собственное локальное хранилище потоков, несмотря на то, что потоки ОС используются для других аспектов потоковой передачи Java. Вместо этого каждый объект Thread хранит (небезопасное для потоков) сопоставление объектов ThreadLocal с их значениями (в отличие от каждого ThreadLocal, имеющего сопоставление объектов Thread со значениями и вызывающего накладные расходы на производительность).

.NET языки: C # и другие

В языках .NET Framework, таких как C #, статические поля могут быть помечены атрибутом ThreadStatic :

class FooBar { [ThreadStatic] частный статический int _foo; }

В.NET 4.0 доступен класс System.Threading.ThreadLocal для выделения и ленивой загрузки локальных переменных потока.

класс FooBar {частный статический System.Threading.ThreadLocal _foo; }

Также доступен API для динамического выделения локальных переменных потока.

Object Pascal

В Object Pascal (Delphi) или Free Pascal можно использовать зарезервированное ключевое слово threadvar вместо 'var' для объявления переменных используя локальное хранилище потока.

var mydata_process: целое число; threadvar mydata_threadlocal: целое число;

Objective-C

В Cocoa, GNUstep и OpenStep каждый объект NSThreadимеет поток -локальный словарь, к которому можно получить доступ через метод потока threadDictionary.

NSMutableDictionary * dict = [[NSThread currentThread] threadDictionary]; dict [@ "Ключ"] = @ "Некоторые данные";

Perl

В Perl потоки были добавлены на поздних этапах развития языка, после того, как большой объем существующего кода уже присутствовал в Comprehensive Perl Archive Network (CPAN). Таким образом, потоки в Perl по умолчанию используют свое собственное локальное хранилище для всех переменных, чтобы минимизировать влияние потоков на существующий код, не поддерживающий потоки. В Perl переменную, совместно используемую потоком, можно создать с помощью атрибута:

использовать потоки; использовать thread :: shared; мой $ localvar; мой $ sharedvar: общий;

PureBasic

В PureBasic переменные потока объявляются с ключевым словом Threaded.

Threaded Var

Python

In Python версии 2.4 или новее, локальный класс в модуле потоковой передачи может использоваться для создания локального хранилища потока.

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

Ruby

Ruby может создавать / получать доступ к локальным переменным потока с помощью = / methods:

Thread.current [: user_id ] = 1

Rust

Rust может создавать / обращаться к локальным переменным потока с помощью thread_local! макрос в модуле std :: thread [стандартной библиотеки Rust]:

использовать std :: cell :: RefCell; используйте std :: thread; thread_local! (статический FOO: RefCell = RefCell :: new (1)); FOO.with (| f | {assert_eq! (* F.borrow (), 1); * f.borrow_mut () = 2;}); // каждый поток запускается с начальным значением 1, даже если этот поток уже изменил свою копию локального значения потока на 2 let t = thread :: spawn (move || {FOO.with (| f | {assert_eq! (* f.borrow (), 1); * f.borrow_mut () = 3;});}); // ждем завершения потока и выходим из паники t.join (). unwrap (); // исходный поток сохраняет исходное значение 2, несмотря на то, что дочерний поток изменяет значение на 3 для этого потока FOO.with (| f | {assert_eq! (* f.borrow (), 2);});
Ссылки
Внешние ссылки
Последняя правка сделана 2021-06-11 10:48:24
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте