Локальное хранилище потока ( TLS ) - это метод компьютерного программирования, который использует статическую или глобальную память, локальную для потока .
В то время как использование глобальных переменных обычно не приветствуется в современном программировании, унаследованные операционные системы, такие как UNIX , разработаны для однопроцессорного оборудования и требуют некоторого дополнительного механизма для сохранения семантики предварительно реентерабельных API. Примером таких ситуаций является ситуация, когда функции используют глобальную переменную для установки условия ошибки (например, глобальная переменная, errno
используемая многими функциями библиотеки C). Если бы errno
это была глобальная переменная, вызов системной функции в одном потоке может перезаписать значение, ранее установленное вызовом системной функции в другом потоке, возможно, до того, как следующий код в этом другом потоке сможет проверить состояние ошибки. Решение состоит в том, чтобы иметьerrno
быть переменной, которая выглядит как глобальная, но на самом деле существует один раз для каждого потока, т. е. находится в локальном хранилище потока. Второй вариант использования - несколько потоков, накапливающих информацию в глобальную переменную. Чтобы избежать состояния гонки , каждый доступ к этой глобальной переменной должен быть защищен мьютексом . В качестве альтернативы каждый поток может накапливаться в локальной переменной потока (которая, по определению, не может быть прочитана или записана из других потоков, подразумевая, что не может быть условий гонки). Затем потокам нужно только синхронизировать окончательное накопление из своей собственной локальной переменной потока в единственную, действительно глобальную переменную.
Многие системы накладывают ограничения на размер блока локальной памяти потока, на самом деле часто довольно жесткие ограничения. С другой стороны, если система может предоставить по крайней мере переменную размером с адрес (указатель) памяти, локальную для потока, то это позволяет использовать блоки памяти произвольного размера локально для потока, динамически выделяя такой блок памяти и сохраняя адрес памяти этого блока в локальной переменной потока. На RISC- машинах соглашение о вызовах часто резервирует регистр указателя потока для этого использования.
Реализация 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).
C и C ++
В C11 ключевое слово _Thread_local
используется для определения локальных переменных потока. Заголовок
, если он поддерживается, определяется thread_local
как синоним этого ключевого слова. Пример использования:
#include thread_local int foo = 0 ;
В C ++ 11 введено ключевое слово thread_local
[2], которое можно использовать в следующих случаях.
- Переменные уровня пространства имен (глобальные)
- Статические переменные файла
- Статические переменные функции
- Статические переменные-члены
Помимо этого, различные реализации компилятора предоставляют определенные способы объявления локальных переменных потока:
- Solaris Studio C / C ++, IBM XL C / C ++, [3] GNU C , [4] Clang [5] и Intel C ++ Compiler (системы Linux) [6] используют синтаксис:
__thread int number;
- Visual C ++ , [7] Intel C / C ++ (системы Windows), [8] C ++ Builder и Digital Mars C ++ используют синтаксис:
__declspec(thread) int number;
- C ++ Builder также поддерживает синтаксис:
int __thread number;
В версиях Windows до Vista и Server 2008 __declspec(thread)
работает с библиотеками DLL, только если эти библиотеки привязаны к исполняемому файлу, и не будет работать с библиотеками, загруженными с помощью LoadLibrary () (может произойти сбой защиты или повреждение данных). [9]
Common Lisp (и, возможно, другие диалекты)
Common Lisp предоставляет функцию, называемую переменными с динамической областью видимости .
Динамические переменные имеют привязку, которая является частной для вызова функции и всех дочерних элементов, вызываемых этой функцией.
Эта абстракция естественным образом отображается в хранилище, зависящее от потока, и реализации Lisp, которые предоставляют потоки, делают это. Common Lisp имеет множество стандартных динамических переменных, поэтому потоки не могут быть разумно добавлены к реализации языка без этих переменных, имеющих локальную для потока семантику в динамическом связывании.
Например, стандартная переменная *print-base*
определяет систему счисления по умолчанию, в которой печатаются целые числа. Если эта переменная переопределена, то весь включающий код будет печатать целые числа в альтернативной системе счисления:
;;; функция foo и ее дочерние элементы будут печатать ;; в шестнадцатеричном формате: ( let (( * print-base * 16 )) ( foo ))
Если функции могут выполняться одновременно в разных потоках, эта привязка должна быть правильно локальна для потока, иначе каждый поток будет бороться за то, кто контролирует глобальную систему счисления печати.
D
В D версии 2 все статические и глобальные переменные по умолчанию являются локальными для потока и объявляются с синтаксисом, аналогичным «нормальным» глобальным и статическим переменным в других языках. Глобальные переменные должны быть явно запрошены с использованием общего ключевого слова:
int threadLocal ; // Это локальная переменная потока. общий int global ; // Это глобальная переменная, совместно используемая всеми потоками.
Разделяет ключевое слово работает и как класс хранения, и в качестве типа классификаторе - общие переменные могут быть некоторые ограничения , которые статически обеспечения целостности данных. [10] Чтобы объявить «классическую» глобальную переменную без этих ограничений, необходимо использовать ключевое слово unsafe __gshared : [11]
__gshared int global ; // Это простая старая глобальная переменная.
Ява
В Java локальные переменные потока реализуются объектом ThreadLocal
класса . ThreadLocal содержит переменную типа T, доступную через методы get / set. Например, переменная ThreadLocal, содержащая целочисленное значение, выглядит так:
частный статический финал ThreadLocal < Integer > myThreadLocalInteger = new ThreadLocal < Integer > ();
По крайней мере, для Oracle / OpenJDK это не использует собственное локальное хранилище потока, несмотря на то, что потоки ОС используются для других аспектов потоковой передачи Java. Вместо этого каждый объект Thread хранит (небезопасное для потоков) сопоставление объектов ThreadLocal с их значениями (в отличие от каждого объекта ThreadLocal, имеющего сопоставление объектов Thread со значениями и вызывающего накладные расходы на производительность). [12]
Языки .NET: C # и другие
В языках .NET Framework, таких как C # , статические поля могут быть помечены атрибутом ThreadStatic :
класс FooBar { [ThreadStatic] частный статический int _foo ; }
В .NET 4.0 доступен класс System.Threading.ThreadLocal
class FooBar { частная статическая система . Резьба . ThreadLocal < int > _foo ; }
Также доступен API для динамического выделения локальных переменных потока.
Object Pascal
В 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 потоки были добавлены на поздних этапах развития языка, после того, как большой объем существующего кода уже присутствовал в Comprehensive Perl Archive Network (CPAN). Таким образом, потоки в Perl по умолчанию берут свое собственное локальное хранилище для всех переменных, чтобы минимизировать влияние потоков на существующий код, не поддерживающий потоки. В Perl переменную, разделяемую потоком, можно создать с помощью атрибута:
использовать нити ; использовать thread :: shared ;мой $ localvar ; мой $ sharedvar : общий ;
PureBasic
В PureBasic переменные потока объявляются с ключевым словом Threaded .
Резьбовой Вар
Python
В Python версии 2.4 или новее локальный класс в модуле потоковой передачи может использоваться для создания локального хранилища потока.
импорт потоков mydata = threading . local () mydata . х = 1
Рубин
Ruby может создавать / обращаться к локальным переменным потока, используя методы [] = / []:
Резьба . текущий [ : user_id ] = 1
Ржавчина
Локальные переменные потока могут быть созданы в Rust с помощью thread_local!
макроса, предоставляемого стандартной библиотекой Rust:
используйте std :: cell :: RefCell ; используйте std :: thread ; thread_local! ( статический FOO : RefCell < u32 > = RefCell :: new ( 1 )); FOO . с ( | f | { assert_eq! ( * е . заимствовать (), 1 ); * f . заимствовать_mut () = 2 ; });// каждый поток запускается с начальным значением 1, даже если этот поток уже изменил свою копию локального значения потока на 2 let t = thread :: spawn ( move || { FOO . с ( | f | { assert_eq! ( * е . заимствовать (), 1 ); * f . заимствовать_mut () = 3 ; });});// ждем завершения потока и выходим из паники t . присоединиться (). разворачивать ();// исходный поток сохраняет исходное значение 2, несмотря на то, что дочерний поток изменяет значение на 3 для этого потока FOO . с ( | f | { assert_eq! ( * е . заимствовать (), 2 ); });
Рекомендации
- ^ Пьетрек, Мэтт (май 2006 г.). «Под капотом» . MSDN . Проверено 6 апреля 2010 года .
- ^ Раздел 3.7.2 в стандарте C ++ 11
- ^ IBM XL C / C ++: Локальное хранилище потока
- ^ GCC 3.3.1: Локальное хранилище потока
- ^ Clang 2.0: примечания к выпуску
- ^ Заметки о выпуске Intel C ++ Compiler 8.1 (linux): локальное хранилище потока
- ^ Visual Studio 2003: модификатор класса расширенного хранилища потока
- ^ Intel C ++ Compiler 10.0 (windows): локальное хранилище потока
- ^ «Правила и ограничения для TLS»
- ^ Александреску, Андрей (6 июля 2010 г.). Глава 13 - Параллелизм . D язык программирования . InformIT. п. 3 . Проверено 3 января 2014 года .
- ^ Брайт, Уолтер (12 мая 2009 г.). «Переход на общий доступ» . dlang.org . Проверено 3 января 2014 года .
- ^ "Как Java ThreadLocal реализован под капотом?" . Переполнение стека . Обмен стеками . Проверено 27 декабря 2015 года .
Внешние ссылки
- Поддержка процессора OpenMP Parallell для определенного оборудования
- Shared_memory Доступ к страницам памяти и конфигурации (поддерживается процессор и / или ядро, если поддерживается)
- Context_switch Также называется переключением задач, потоки, страницы ускоряются аппаратно и / или предоставляются ядром
- Semaphore_ (программирование) LOCK, если (cpu) недостаточно поддерживает многопортовую память (предотвращение зависания процессора)
- Обработка ELF для локального хранилища потоков - документ о реализации на C или C ++ .
- ACE_TSS Ссылка на шаблон класса
- Документация по шаблону класса RWTThreadLocal
- Статья Дуга Доденса « Использование локального хранилища потоков для передачи данных, специфичных для потоков »
- « Локальное хранилище потоков », Лоуренс Кроул
- Статья Уолтера Брайта «Не всегда приятно делиться »
- Практическое использование ThreadLocal в Java: http://www.captechconsulting.com/blogs/a-persistence-pattern-using-threadlocal-and-ejb-interceptors
- GCC " [1] "
- Ржавчина " [2] "