В компьютерном программировании локальное хранилище потока ( TLS ) — это метод управления памятью , который использует статическую или глобальную память , локальную для потока . Эта концепция позволяет хранить глобальные данные в системе с отдельными потоками.
Многие системы накладывают ограничения на размер блока локальной памяти потока, причем зачастую это довольно жесткие ограничения. С другой стороны, если система может предоставить по крайней мере переменную размером с адрес памяти (указатель), локальную для потока, то это позволяет использовать блоки памяти произвольного размера локальным для потока способом, динамически распределяя такой блок памяти и сохраняя его. адрес памяти этого блока в локальной переменной потока. На машинах RISC соглашение о вызовах часто резервирует для этого использования регистр указателя потока .
Хотя использование глобальных переменных в современном программировании обычно не рекомендуется, некоторые старые операционные системы, такие как UNIX , изначально были разработаны для однопроцессорного оборудования и часто используют глобальные переменные для хранения важных значений. Примером может служить errno
использование многими функциями библиотеки C. На современной машине, где несколько потоков могут изменять переменную errno
, вызов системной функции в одном потоке может перезаписать значение, ранее установленное вызовом системной функции в другом потоке, возможно, до того, как следующий код в этом другом потоке сможет проверьте состояние ошибки. Решение состоит в том, чтобы errno
создать переменную, которая выглядела бы так, как будто она глобальная, но физически хранилась бы в пуле памяти для каждого потока, локальном хранилище потока.
Вторым вариантом использования может быть несколько потоков, накапливающих информацию в глобальной переменной. Чтобы избежать состояния гонки , каждый доступ к этой глобальной переменной должен быть защищен мьютексом . Вместо этого каждый поток может накапливаться в локальной переменной потока, тем самым исключая любую возможность состояния гонки и тем самым устраняя необходимость блокировки. Затем потокам остается только синхронизировать окончательное накопление из своей собственной локальной переменной потока в одну глобальную переменную.
Функцию интерфейса прикладного программирования (API) TlsAlloc
можно использовать для получения индекса неиспользуемого слота TLS ; индекс слота TLS тогда будет считаться «использованным».
Затем функции TlsGetValue
и TlsSetValue
используются для чтения и записи адреса памяти в локальную переменную потока, идентифицируемую индексом слота TLS . TlsSetValue
влияет только на переменную текущего потока. Функцию TlsFree
можно вызвать для освобождения индекса слота TLS .
Для каждого потока существует блок информации о потоке Win32 . Одна из записей в этом блоке — это таблица локального хранилища для этого потока. [1] TlsAlloc возвращает индекс этой таблицы, уникальный для каждого адресного пространства, для каждого вызова. Каждый поток имеет собственную копию таблицы локального хранилища потока. Следовательно, каждый поток может независимо использовать TlsSetValue(index) и получать указанное значение через TlsGetValue(index), поскольку они устанавливают и ищут запись в собственной таблице потока.
Помимо семейства функций TlsXxx, исполняемые файлы Windows могут определять раздел, который отображается на другую страницу для каждого потока исполняемого процесса. В отличие от значений TlsXxx, эти страницы могут содержать произвольные и действительные адреса. Однако эти адреса различны для каждого исполняемого потока и поэтому не должны передаваться асинхронным функциям (которые могут выполняться в другом потоке) или иным образом передаваться в код, который предполагает, что виртуальный адрес уникален в пределах всего процесса. Разделы TLS управляются с помощью подкачки памяти , а их размер квантуется до размера страницы (4 КБ на машинах x86). Такие разделы могут быть определены только внутри основного исполняемого файла программы — библиотеки DLL не должны содержать такие разделы, поскольку они неправильно инициализируются при загрузке с помощью LoadLibrary.
В API Pthreads локальная для потока память обозначается термином «данные, специфичные для потока».
Функции pthread_key_create
и pthread_key_delete
используются соответственно для создания и удаления ключа для данных, специфичных для потока. Тип ключа явно оставлен непрозрачным и называется pthread_key_t
. Этот ключ виден всем потокам. В каждом потоке ключ может быть связан с данными, специфичными для потока, через pthread_setspecific
. Данные можно позже получить с помощью pthread_getspecific
.
Кроме того, pthread_key_create
при желании можно принять функцию деструктора, которая будет автоматически вызываться при выходе потока, если данные, специфичные для потока, не равны NULL . Деструктор получает значение, связанное с ключом, в качестве параметра, чтобы он мог выполнять действия по очистке (закрытие соединений, освобождение памяти и т. д.). Даже если указан деструктор, программа все равно должна вызвать pthread_key_delete
освобождение данных, специфичных для потока, на уровне процесса (деструктор освобождает только данные, локальные для потока).
Помимо того, что программисты будут вызывать соответствующие функции API, также можно расширить язык программирования для поддержки локального хранилища потоков (TLS).
В C11 это ключевое слово _Thread_local
используется для определения локальных переменных потока. Заголовок <threads.h>
, если он поддерживается, определяется thread_local
как синоним этого ключевого слова. Пример использования:
#include <threads.h> thread_local int foo = 0 ;
В C++11 представлено ключевое слово thread_local
[2] , которое можно использовать в следующих случаях.
Помимо этого, различные реализации компилятора предоставляют особые способы объявления локальных переменных потока:
__thread int number;
__declspec(thread) int number;
int __thread number;
В версиях Windows до Vista и Server 2008 __declspec(thread)
работает в библиотеках DLL только в том случае, если эти библиотеки DLL привязаны к исполняемому файлу, и не будет работать для тех, которые загружены с помощью LoadLibrary() (может произойти сбой защиты или повреждение данных). [9]
Common Lisp предоставляет функцию, называемую переменными с динамической областью действия .
Динамические переменные имеют привязку, которая является частной для вызова функции и всех дочерних элементов, вызываемых этой функцией.
Эта абстракция естественным образом отображается в хранилище, специфичное для потоков, и реализации Lisp, предоставляющие потоки, делают это. Common Lisp имеет множество стандартных динамических переменных, и поэтому потоки не могут быть разумно добавлены в реализацию языка, если эти переменные не имеют локальной семантики потока в динамическом связывании.
Например, стандартная переменная *print-base*
определяет систему счисления по умолчанию, в которой печатаются целые числа. Если эта переменная переопределена, то весь включающий код будет печатать целые числа в альтернативной системе счисления:
;;; функция foo и ее дочерние элементы будут печатать ;; в шестнадцатеричном формате: ( let (( *print-base* 16 )) ( foo ))
Если функции могут выполняться одновременно в разных потоках, эта привязка должна быть должным образом локальной для потока, иначе каждый поток будет бороться за то, кто контролирует глобальную систему счисления печати.
В версии D 2 все статические и глобальные переменные по умолчанию являются локальными для потока и объявляются с синтаксисом, аналогичным «обычным» глобальным и статическим переменным в других языках. Глобальные переменные должны быть явно запрошены с использованием общего ключевого слова:
int threadLocal ; // Это локальная переменная потока. общий int global ; // Это глобальная переменная, общая для всех потоков.
Ключевое слово common работает и как класс хранения, и как квалификатор типа — на общие переменные распространяются некоторые ограничения, которые статически обеспечивают целостность данных. [12] Чтобы объявить «классическую» глобальную переменную без этих ограничений, необходимо использовать ключевое слово unsafe __gshared : [13]
__gshared int глобальный ; // Это старая добрая глобальная переменная.
В Java локальные переменные потока реализуются объектом ThreadLocal
класса . [14] ThreadLocal содержит переменную типа T, [14] доступ к которой осуществляется с помощью методов get/set. Например, переменная ThreadLocal, содержащая целочисленное значение, выглядит следующим образом:
частный статический окончательный ThreadLocal <Integer> myThreadLocalInteger = новый ThreadLocal <Integer> ( ) ; _ _
По крайней мере, для Oracle/OpenJDK здесь не используется собственное локальное хранилище потоков, несмотря на то, что потоки ОС используются для других аспектов потоковой обработки Java. Вместо этого каждый объект Thread хранит (не поддерживающую потокобезопасность) карту объектов ThreadLocal с их значениями (в отличие от того, что каждый объект ThreadLocal имеет карту объектов Thread со значениями и приводит к снижению производительности). [15]
В языках .NET Framework , таких как C# , статические поля могут быть помечены атрибутом ThreadStatic: [16] : 898.
класс FooBar { [ThreadStatic] Private static int _foo ; }
В .NET Framework 4.0 доступен класс System.Threading.ThreadLocal<T> для выделения и ленивой загрузки локальных переменных потока. [16] : 899
класс FooBar { частная статическая система . Резьба . ThreadLocal <int> _foo ; _ _ }
Также доступен API для динамического выделения локальных переменных потока. [16] : 899–890.
В Object Pascal ( Delphi ) или Free Pascal зарезервированное ключевое слово threadvar может использоваться вместо ' var' для объявления переменных с использованием локального хранилища потока.
вар mydata_process : целое число ; threadvar mydata_threadlocal : целое число ;
В Cocoa , GNUstep и OpenStep каждый NSThread
объект имеет локальный словарь потока, доступ к которому можно получить через threadDictionary
метод потока.
NSMutableDictionary * dict = [[ NSThread currentThread ] threadDictionary ]; dict [ @"Ключ" ] = @"Некоторые данные" ;
Потоки в Perl были добавлены на позднем этапе развития языка, после того как большой объем существующего кода уже присутствовал в Комплексной сети архивов Perl (CPAN). Таким образом, потоки в Perl по умолчанию занимают собственное локальное хранилище для всех переменных, чтобы минимизировать влияние потоков на существующий код, не поддерживающий потоки. В Perl переменную, совместно используемую потоком, можно создать с помощью атрибута:
использовать потоки ; используйте темы::shared ; моя $localvar ; мой $sharedvar : общий ;
В PureBasic переменные потока объявляются с помощью ключевого слова Threaded.
Резьбовой вар
В Python версии 2.4 или новее локальный класс в модуле потоков можно использовать для создания локального хранилища потока.
импортировать потоки mydata = threading . локальный () mydata . х = 1
Можно создать несколько экземпляров локального класса для хранения разных наборов переменных. [17] Таким образом, это не синглтон .
Ruby может создавать локальные переменные потока и получать к ним доступ, используя методы []=/[]:
Нить . текущий [ :user_id ] = 1
Локальные переменные потока можно создать в Rust с помощью thread_local!
макроса, предоставляемого стандартной библиотекой Rust:
используйте std :: cell :: RefCell ; используйте std :: thread ; поток_локальный! ( статический FOO : RefCell <u32> = RefCell :: new ( 1 ) ) ; ФУ . with ( | f | { Assert_eq! ( * f . заимствовать (), 1 ); * f . заимствовать_мут () = 2 ; }); // каждый поток начинается с начального значения 1, хотя этот поток уже изменил свою копию локального значения потока на 2 let t = thread :: spawn ( move || { FOO . with ( | f | { Assert_eq! ( * f . заимствовать (), 1 ); * f . заимствовать_мут () = 3 ; }); }); // ждем завершения потока и выходим из паники t . присоединиться (). развернуть ();// исходный поток сохраняет исходное значение 2, несмотря на то, что дочерний поток изменил значение на 3 для этого потока FOO . with ( | f | { Assert_eq! ( * f . заимствовать (), 2 ); });
В этом документе описаны языковые расширения, предоставляемые Clang.
В дополнение к перечисленным здесь языковым расширениям Clang стремится поддерживать широкий спектр расширений GCC.
Дополнительную информацию об этих расширениях см. в руководстве GCC.
{{cite book}}
: |website=
игнорируется ( помощь )