Локальное хранилище потока
Эта статья нуждается в дополнительных цитатах для проверки . ( декабрь 2012 г. ) |
В компьютерном программировании локальное хранилище потока ( TLS ) — это метод управления памятью , который использует статическую или глобальную память, локальную для потока . Эта концепция позволяет хранить глобальные данные в системе с отдельными потоками.
Многие системы накладывают ограничения на размер блока локальной памяти потока, причем зачастую это довольно жесткие ограничения. С другой стороны, если система может предоставить, по крайней мере, локальную для потока переменную размером с адрес памяти (указатель), то это позволяет использовать блоки памяти произвольного размера локальным для потока способом, динамически распределяя такой блок памяти и сохраняя его. адрес памяти этого блока в локальной переменной потока. На RISC машинах соглашение о вызовах часто резервирует для этого использования регистр указателя потока .
Использование
[ редактировать ]Хотя использование глобальных переменных в современном программировании обычно не рекомендуется, некоторые старые операционные системы, такие как UNIX, изначально были разработаны для однопроцессорного оборудования и часто используют глобальные переменные для хранения важных значений. Примером является errno
используется многими функциями C. библиотеки На современной машине, где несколько потоков могут изменять errno
переменной, вызов системной функции в одном потоке может перезаписать значение, ранее установленное вызовом системной функции в другом потоке, возможно, до того, как следующий код в этом другом потоке сможет проверить наличие ошибки. Решение состоит в том, чтобы иметь errno
быть переменной, которая выглядит как глобальная, но физически хранится в пуле памяти для каждого потока, локальном хранилище потока.
Вторым вариантом использования может быть несколько потоков, накапливающих информацию в глобальной переменной. Чтобы избежать состояния гонки , каждый доступ к этой глобальной переменной должен быть защищен мьютексом . Вместо этого каждый поток может накапливаться в локальной переменной потока, тем самым исключая любую возможность состояния гонки и тем самым устраняя необходимость в блокировке. Затем потокам остается только синхронизировать окончательное накопление из своей собственной локальной переменной потока в одну глобальную переменную.
Реализация Windows
[ редактировать ]Функция интерфейса прикладного программирования (API) TlsAlloc
может использоваться для получения индекса неиспользуемого слота TLS ; индекс слота TLS тогда будет считаться «использованным».
The 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;
И С11, <threads.h>
также определяет ряд функций для получения, изменения и уничтожения локального хранилища потока, используя имена, начинающиеся с tss_
. В С23, thread_local
само становится ключевым словом. [2]
C++11 представляет thread_local
[3] ключевое слово, которое можно использовать в следующих случаях
- Переменные уровня пространства имен (глобальные)
- Статические переменные файла
- Статические переменные функции
- Статические переменные-члены
Помимо этого, различные реализации компилятора предоставляют особые способы объявления локальных переменных потока:
- Солярис Студия C/С++, [4] IBM XL C/С++, [5] ГНУ С , [6] llvm-gcc, [7] Кланг , [8] и компилятор Intel C++ (системы Linux) [9] используйте синтаксис:
__thread int number;
- Визуальный С++ , [10] Intel C/C++ (системы Windows), [11] C++Builder , Кланг, [12] и Digital Mars C++ используют синтаксис:
__declspec(thread) int number;
- C++Builder также поддерживает синтаксис:
int __thread number;
В версиях Windows до Vista и Server 2008: __declspec(thread)
работает в DLL только тогда, когда эти DLL привязаны к исполняемому файлу, и не будет работать для тех, которые загружены с помощью LoadLibrary() (может произойти сбой защиты или повреждение данных). [10]
Общий Лисп и другие диалекты
[ редактировать ]Common Lisp предоставляет функцию, называемую переменными с динамической областью действия .
Динамические переменные имеют привязку, которая является частной для вызова функции и всех дочерних элементов, вызываемых этой функцией.
Эта абстракция естественным образом отображается в хранилище, специфичное для потоков, и реализации Lisp, предоставляющие потоки, делают это. Common Lisp имеет множество стандартных динамических переменных, и поэтому потоки не могут быть разумно добавлены в реализацию языка, если эти переменные не имеют локальной семантики потока в динамическом связывании.
Например, стандартная переменная *print-base*
определяет систему счисления по умолчанию, в которой печатаются целые числа. Если эта переменная переопределена, то весь включающий код будет печатать целые числа в альтернативной системе счисления:
;;; function foo and its children will print
;; in hexadecimal:
(let ((*print-base* 16)) (foo))
Если функции могут выполняться одновременно в разных потоках, эта привязка должна быть должным образом локальной для потока, иначе каждый поток будет бороться за то, кто контролирует глобальную систему счисления печати.
Д
[ редактировать ]В версии D 2 все статические и глобальные переменные по умолчанию являются локальными для потока и объявляются с синтаксисом, похожим на «обычные» глобальные и статические переменные в других языках. Глобальные переменные должны быть явно запрошены с использованием общего ключевого слова:
int threadLocal; // This is a thread-local variable.
shared int global; // This is a global variable shared with all threads.
Ключевое слово common работает и как класс хранения, и как квалификатор типа — на общие переменные распространяются некоторые ограничения, которые статически обеспечивают целостность данных. [13] Чтобы объявить «классическую» глобальную переменную без этих ограничений, unsafe __gshared : необходимо использовать ключевое слово [14]
__gshared int global; // This is a plain old global variable.
Ява
[ редактировать ]В Java локальные переменные потока реализуются с помощью ThreadLocal
объект класса . [15] ThreadLocal содержит переменную типа T, [15] который доступен через методы get/set. Например, переменная ThreadLocal, содержащая целочисленное значение, выглядит следующим образом:
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
class FooBar
{
[ThreadStatic]
private static int _foo;
}
В .NET Framework 4.0 System.Threading.ThreadLocal<T> для выделения и ленивой загрузки локальных переменных потока. доступен класс [17] : 899
class FooBar
{
private static System.Threading.ThreadLocal<int> _foo;
}
Также API для динамического выделения локальных переменных потока. доступен [17] : 899–890
Объектный Паскаль
[ редактировать ]В Object Pascal ( Delphi ) или Free Pascal зарезервированное ключевое слово threadvar можно использовать вместо 'var' для объявления переменных с использованием локального хранилища потока.
var
mydata_process: integer;
threadvar
mydata_threadlocal: integer;
Цель-C
[ редактировать ]В Cocoa , GNUstep и OpenStep каждый NSThread
объект имеет локальный словарь потока, доступ к которому можно получить через threadDictionary
метод.
NSMutableDictionary *dict = [[NSThread currentThread] threadDictionary];
dict[@"A key"] = @"Some data";
Перл
[ редактировать ]Потоки в Perl были добавлены на позднем этапе развития языка, после того как большой объем существующего кода уже присутствовал в Комплексной сети архивов Perl (CPAN). Таким образом, потоки в Perl по умолчанию занимают собственное локальное хранилище для всех переменных, чтобы минимизировать влияние потоков на существующий код, не поддерживающий потоки. В Perl переменную, совместно используемую потоком, можно создать с помощью атрибута:
use threads;
use threads::shared;
my $localvar;
my $sharedvar :shared;
PureBasic
[ редактировать ]В PureBasic переменные потока объявляются с помощью ключевого слова Threaded .
Threaded Var
Питон
[ редактировать ]В Python версии 2.4 или более поздней версии локальный класс в модуле потоков можно использовать для создания локального хранилища потока.
import threading
mydata = threading.local()
mydata.x = 1
Можно создать несколько экземпляров локального класса для хранения разных наборов переменных. [18] Таким образом, это не синглтон .
Руби
[ редактировать ]Ruby может создавать локальные переменные потока и получать к ним доступ, используя []=
/ []
методы:
Thread.current[:user_id] = 1
Ржавчина
[ редактировать ]Локальные переменные потока могут быть созданы в Rust с помощью thread_local!
макрос, предоставляемый стандартной библиотекой Rust:
use std::cell::RefCell;
use std::thread;
thread_local!(static FOO: RefCell<u32> = RefCell::new(1));
FOO.with(|f| {
assert_eq!(*f.borrow(), 1);
*f.borrow_mut() = 2;
});
// each thread starts out with the initial value of 1, even though this thread already changed its copy of the thread local value to 2
let t = thread::spawn(move || {
FOO.with(|f| {
assert_eq!(*f.borrow(), 1);
*f.borrow_mut() = 3;
});
});
// wait for the thread to complete and bail out on panic
t.join().unwrap();
// original thread retains the original value of 2 despite the child thread changing the value to 3 for that thread
FOO.with(|f| {
assert_eq!(*f.borrow(), 2);
});
См. также
[ редактировать ]- OpenMP — еще одно средство многопроцессорной обработки с общей памятью, которое поддерживает попоточное хранение с помощью «предложений атрибутов совместного использования данных» (см. §Пункты ).
Ссылки
[ редактировать ]- ^ Пьетрек, Мэтт (май 2006 г.). «Под капотом» . MSDN . Проверено 6 апреля 2010 г.
- ^ «Библиотека поддержки параллелизма — cppreference.com» . ru.cppreference.com .
- ^ Раздел 3.7.2 в стандарте C++11.
- ^ «Информация о компиляторе C, относящаяся к реализации Sun» . Руководство пользователя C Sun Studio 8 . 2004. 2.3 Спецификатор локального хранилища потока.
- ^ «Компиляторы XL C/C++» . Август 2010 г. Локальное хранилище потоков (TLS). Архивировано из оригинала 11 апреля 2011 года.
- ^ «Локальное хранилище потока» . GCC 3.3.1 Руководство . 2003.
- ^ «Примечания к выпуску LLVM 2.0» . 23 мая 2007 г. Улучшения llvm-gcc.
- ^ «Расширения языка Clang — документация Clang 3.8» . Введение.
В этом документе описаны языковые расширения, предоставляемые Clang. В дополнение к перечисленным здесь языковым расширениям Clang стремится поддерживать широкий спектр расширений GCC. Дополнительную информацию об этих расширениях см. в руководстве GCC.
- ^ «Компилятор Intel® C++ 8.1 для Linux: примечания к выпуску для процессоров Intel IA-32 и Itanium®» (PDF) . 2004. Локальное хранилище потоков. Архивировано из оригинала (PDF) 19 января 2015 года.
- ↑ Перейти обратно: Перейти обратно: а б Визуальная Студия 2003: «Локальное хранилище потоков (TLS)» . Документы Майкрософт . 5 июня 2017 г.
- ^ Компилятор Intel C++ 10.0 (Windows): локальное хранилище потоков
- ^ «Атрибуты в Clang — документация Clang 3.8» . нить.
- ^ Александреску, Андрей (6 июля 2010 г.). Глава 13. Параллелизм . Сообщите ИТ-специалистам. п. 3 . Проверено 3 января 2014 г.
{{cite book}}
:|website=
игнорируется ( помогите ) - ^ Брайт, Уолтер (12 мая 2009 г.). «Миграция на общий доступ» . dlang.org . Проверено 3 января 2014 г.
- ↑ Перейти обратно: Перейти обратно: а б Блох 2018 , с. 151-155, §пункт 33. Рассмотрите типобезопасные гетерогенные контейнеры.
- ^ «Как реализован Java ThreadLocal?» . Переполнение стека . Обмен стеками . Проверено 27 декабря 2015 г.
- ↑ Перейти обратно: Перейти обратно: а б с Альбахари 2022 .
- ^ «cpython/Lib/_threading_local.py в версии 3.12 · python/cpython» . Гитхаб . Проверено 25 октября 2023 г.
Библиография
[ редактировать ]- Альбахари, Джозеф (2022). C# 10 в двух словах (первое изд.). О'Рейли. ISBN 978-1-098-12195-2 .
- Блох, Джошуа (2018). «Эффективная Java: Руководство по языку программирования» (третье изд.). Аддисон-Уэсли. ISBN 978-0134685991 .
Внешние ссылки
[ редактировать ]- Обработка ELF для локального хранилища потока — документ о реализации на C или C++ .
- ACE_TSS< TYPE > Справочник шаблонов классов
- Документация по шаблону класса RWTThreadLocal<Type>
- Статья « Использование локального хранилища потока для передачи данных, специфичных для потока ». Дуга Доденса
- « Локальное хранилище потоков » Лоуренса Кроула
- Статья « Не всегда приятно делиться » Уолтера Брайта
- Практическое использование ThreadLocal в Java: http://www.captechconsulting.com/blogs/a-persistence-pattern-using-threadlocal-and-ejb-interceptors
- ССЗ " [1] "
- Ржавчина " [2] "