Локальное хранилище потока - Thread-local storage

Локальное хранилище потока (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

в Pthreads API, локальная для потока память обозначается термином "Данные, специфичные для потока".

Функции 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 используется для определения локальных переменных потока. Заголовок <threads.h>, если поддерживается, определяет thread_local как синоним этого ключевого слова. Пример использования:

#включают <threads.h>thread_local int фу = 0;

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

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

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

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

Общий Лисп (и, возможно, другие диалекты)

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

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

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

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

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

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

D

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

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

В общий ключевое слово работает и как класс хранения, и как квалификатор типаобщий переменные подвержены некоторым ограничениям, которые статически обеспечивают целостность данных.[10] Чтобы объявить "классическую" глобальную переменную без этих ограничений, небезопасный __gshared необходимо использовать ключевое слово:[11]

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

Ява

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

частный статический окончательный ThreadLocal<Целое число> myThreadLocalInteger = новый ThreadLocal<Целое число>();

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

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

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

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

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

класс FooBar{    частный статический Система.Резьба.ThreadLocal<int> _foo;}

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

Object Pascal

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

вар   mydata_process: целое число;резьба   mydata_threadlocal: целое число;

Цель-C

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

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

Perl

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

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

PureBasic

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

РезьбовойВар

Python

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

импорт заправкамои данные = заправка.местный()мои данные.Икс = 1

Рубин

Рубин может создавать / обращаться к локальным переменным потока, используя методы [] = / []:

Нить.текущий[:Логин пользователя] = 1

Ржавчина

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

использоватьстандартное::ячейка::RefCell;использоватьстандартное::нить;thread_local!(статическийFOO: RefCell<u32>=RefCell::новый(1));FOO.с участием(|ж|{assert_eq!(*ж.одолжить(),1);*ж.заимствовать_mut()=2;});// каждый поток запускается с начальным значением 1, даже если этот поток уже изменил свою копию локального значения потока на 2позволятьт=нить::порождать(шаг||{FOO.с участием(|ж|{assert_eq!(*ж.одолжить(),1);*ж.заимствовать_mut()=3;});});// ждем завершения потока и выходим из паникит.присоединиться().развернуть();// исходный поток сохраняет исходное значение 2, несмотря на то, что дочерний поток изменяет значение на 3 для этого потокаFOO.с участием(|ж|{assert_eq!(*ж.одолжить(),2);});

использованная литература

  1. ^ Пьетрек, Мэтт (Май 2006 г.). "Под капотом". MSDN. Получено 6 апреля 2010.
  2. ^ Раздел 3.7.2 в стандарте C ++ 11
  3. ^ IBM XL C / C ++: Локальное хранилище потока
  4. ^ GCC 3.3.1: Локальное хранилище потоков
  5. ^ Clang 2.0: примечания к выпуску
  6. ^ Примечания к выпуску Intel C ++ Compiler 8.1 (linux): Локальное хранилище потока
  7. ^ Visual Studio 2003: Модификатор класса расширенной памяти потока
  8. ^ Компилятор Intel C ++ 10.0 (windows): Локальное хранилище потока
  9. ^ «Правила и ограничения для TLS»
  10. ^ Александреску, Андрей (6 июля 2010 г.). Глава 13 - Параллелизм. Язык программирования D. InformIT. п. 3. Получено 3 января 2014.
  11. ^ Брайт, Уолтер (12 мая 2009 г.). «Переход на общий доступ». dlang.org. Получено 3 января 2014.
  12. ^ «Как Java ThreadLocal реализован под капотом?». Переполнение стека. Обмен стеком. Получено 27 декабря 2015.

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