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 ;     

В C11 <threads.h>также определяет ряд функций для извлечения, изменения и уничтожения локального хранилища потока, используя имена, начинающиеся с tss_. В C23 thread_localсамо становится ключевым словом. [2]

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

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

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

Common Lisp и другие диалекты

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

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

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

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

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

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

Д

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

int threadLocal ; // Это локальная переменная потока. shared int global ; // Это глобальная переменная, общая для всех потоков.     

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

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

Ява

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

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

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

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

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

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

В .NET Framework 4.0 класс System.Threading.ThreadLocal<T> доступен для выделения и ленивой загрузки локальных переменных потока. [17] : 899 

класс FooBar { private static System.Threading.ThreadLocal <int> _foo ; }     

Также доступен API для динамического выделения локальных переменных потока. [17] : 899–890 

Объектный Паскаль

В 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 [ @"A key" ] = @"Some data" ;       

Перл

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

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

ЧистыйБазовый

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

Резьбовой Var

Питон

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

импорт  потоков mydata  =  потоки . local () mydata . x  =  1

Можно создать несколько экземпляров локального класса для хранения различных наборов переменных. [18] Таким образом, это не синглтон .

Рубин

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

Тема . текущий [ :user_id ] = 1  

Ржавчина

Локальные переменные потока можно создавать в Rust с помощью thread_local!макроса, предоставляемого стандартной библиотекой Rust:

использовать std :: cell :: RefCell ; использовать std :: thread ;  thread_local! ( static FOO : RefCell < u32 > = RefCell :: new ( 1 ));   FOO.with ( | f | { assert_eq! ( * f.remove ( ) , 1 ) ; * f.remove_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 ); });   

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

Ссылки

  1. ^ Pietrek, Matt (май 2006). "Under the Hood". MSDN . Получено 6 апреля 2010 .
  2. ^ "Библиотека поддержки параллелизма - cppreference.com". en.cppreference.com .
  3. ^ Раздел 3.7.2 в стандарте C++11
  4. ^ «Информация о компиляторе C, специфичная для реализации Sun». Руководство пользователя C Sun Studio 8. 2004. 2.3 Указатель локального хранилища потока.
  5. ^ "XL C/C++ compilers". Август 2010. Локальное хранилище потока (TLS). Архивировано из оригинала 11 апреля 2011.
  6. ^ "Локальное хранилище потока". Руководство GCC 3.3.1 . 2003.
  7. ^ "Заметки о выпуске LLVM 2.0". 23 мая 2007 г. Улучшения llvm-gcc.
  8. ^ "Clang Language Extensions - Clang 3.8 documentation". Введение. В этом документе описываются языковые расширения, предоставляемые Clang. В дополнение к языковым расширениям, перечисленным здесь, Clang стремится поддерживать широкий спектр расширений GCC. Более подробную информацию об этих расширениях см. в руководстве GCC.
  9. ^ "Intel® C++ Compiler 8.1 for Linux Release Notes For Intel IA-32 and Itanium® Processors" (PDF) . 2004. Thread-local Storage. Архивировано из оригинала (PDF) 19 января 2015 г.
  10. ^ ab Visual Studio 2003: "Локальное хранилище потока (TLS)". Microsoft Docs . 5 июня 2017 г.
  11. ^ Intel C++ Compiler 10.0 (windows): локальное хранилище потока
  12. ^ "Атрибуты в Clang - документация Clang 3.8". ветка.
  13. Александреску, Андрей (6 июля 2010 г.). Глава 13. Параллелизм. ИнформИТ. п. 3 . Проверено 3 января 2014 г. {{cite book}}: |website=проигнорировано ( помощь )
  14. Брайт, Уолтер (12 мая 2009 г.). «Переход на общий доступ». dlang.org . Получено 3 января 2014 г.
  15. ^ ab Bloch 2018, стр. 151-155, §Пункт 33: Рассмотрите возможность использования типобезопасных гетерогенных контейнеров.
  16. ^ «Как реализован ThreadLocal в Java изнутри?». Stack Overflow . Stack Exchange . Получено 27 декабря 2015 г. .
  17. ^ abc Albahari 2022.
  18. ^ "cpython/Lib/_threading_local.py в 3.12 · python/cpython". GitHub . Получено 25 октября 2023 г. .

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


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