stringtranslate.com

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

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

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

Применение

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

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

Реализация Windows

Функцию интерфейса прикладного программирования (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.

Реализация 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).

С и С++

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

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

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

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

В версиях 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: C# и другие.

В языках .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 : целое число ;    

Цель-C

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

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

Перл

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

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

PureBasic

В 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 ); });   

Смотрите также

Рекомендации

  1. ^ Пьетрек, Мэтт (май 2006 г.). "Под капотом". MSDN . Проверено 6 апреля 2010 г.
  2. ^ Раздел 3.7.2 в стандарте C++11.
  3. ^ «Информация о C-компиляторе, относящаяся к реализации Sun» . Руководство пользователя C Sun Studio 8 . 2004. 2.3 Спецификатор локального хранилища потока.
  4. ^ «Компиляторы XL C/C++». Август 2010 г. Локальное хранилище потоков (TLS). Архивировано из оригинала 11 апреля 2011 года.
  5. ^ "Локальное хранилище потока" . GCC 3.3.1 Руководство . 2003.
  6. ^ «Примечания к выпуску LLVM 2.0» . 23 мая 2007 г. Улучшения llvm-gcc.
  7. ^ «Расширения языка Clang — документация Clang 3.8» . Введение. В этом документе описаны языковые расширения, предоставляемые Clang. В дополнение к перечисленным здесь языковым расширениям Clang стремится поддерживать широкий спектр расширений GCC. Дополнительную информацию об этих расширениях см. в руководстве GCC.
  8. ^ «Компилятор Intel® C++ 8.1 для Linux: примечания к выпуску для процессоров Intel IA-32 и Itanium®» (PDF) . 2004. Локальное хранилище потоков. Архивировано из оригинала (PDF) 19 января 2015 года.
  9. ^ ab Visual Studio 2003: «Локальное хранилище потоков (TLS)». Документы Майкрософт . 5 июня 2017 г.
  10. ^ Компилятор Intel C++ 10.0 (Windows): локальное хранилище потоков
  11. ^ «Атрибуты в Clang — документация Clang 3.8» . нить.
  12. Александреску, Андрей (6 июля 2010 г.). Глава 13. Параллелизм. ИнформИТ. п. 3 . Проверено 3 января 2014 г. {{cite book}}: |website=игнорируется ( помощь )
  13. Брайт, Уолтер (12 мая 2009 г.). «Миграция на общий доступ». dlang.org . Проверено 3 января 2014 г.
  14. ^ аб Блох 2018, с. 151-155, §пункт 33. Рассмотрите типобезопасные гетерогенные контейнеры.
  15. ^ "Как Java ThreadLocal реализован под капотом?" Переполнение стека . Обмен стеками . Проверено 27 декабря 2015 г.
  16. ^ abc Альбахари 2022.
  17. ^ "cpython/Lib/_threading_local.py в версии 3.12 · python/cpython". Гитхаб . Проверено 25 октября 2023 г.

Библиография


Внешние ссылки