ThreadVar — Ключевое слово Delphi

Вопрос по Delphi threadvar

Когда объявлен threadvar , когда эта переменная будет инициализирована (объект создан)? Это происходит при первом присвоении var? Например:

Что произойдет, если я попытаюсь использовать этот var вне потока после того, как поток установил значение для var? Например:

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

Threadvars может быть завершена или не может быть завершена. Это зависит от того, насколько RTL получает уведомление о завершении потока. По этой причине, вероятно, лучше не хранить динамически распределенные типы (включенные строки) в threadvars. Вместо этого используйте переменную экземпляра объекта TThread для хранения данных, относящихся к потоку.

Вторая часть вашего вопроса — глупость. Он выполняет выполнение кода в потоке после того, как поток уже завершен. Нет такой вещи, как запуск кода «вне потока». Весь код работает в потоках. Каждая программа имеет хотя бы один поток.

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

Ваш вызов ShowMessage отобразит значение, принадлежащее текущему потоку, а не конец, который уже завершен.

ThreadVar — Ключевое слово Delphi

В данной статье мы опишем так называемую локальную память потока (TLS, Thread Local Storage).

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

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

Конечно, данный пример весьма прост. К более сложным примерам можно отнести алгоритм решения системы линейных уравнений методом Гаусса, который использует глобальный массив. Попытка решить одновременно две системы в разных потоках приведет к тому, что данные в матрице исказяться и системы будут решены неправильно.

Какие же выходы из создавшегося положения, кроме как переделывания всех алгоритмов с учетом многопоточности, предоставляет нам Delphi, Win API, и как реализована в Delphi поддержка TLS — вот предмет нашего дальнейшего рассмотрения.

Использование локальной памяи потока в Delphi
Как можно решить приведенную выше проблему? Самое простое — реализовать возможность описания таких переменных, чтобы в каждом новом потоке создавалась из отдельная копия. Нетрудно догадаться, что такая возможность имеется в Delphi. Для описания таких переменных используется ключевое слово threadvar. Запустите программу TLSDemo02.dpr, чтобы убедиться в том, что она работает корректно, а ведь она отдичается от программы TLSDemo01.dpr только шестью символами в строке 6!

Из этой особенности переменных, описанных в секции threadvar, вытекают также следующие ограничения:

эти переменные не могут иметь начальное значение;
эти переменные не могут быть локальными;
к этим переменных не применима директива absolute;
при описания обычных переменных в секции absolute нельзя ссылаться на переменные threadvar.
Все сказанное проиллюстрировано ниже: Тем не менее, описание переменных в секции threadvar не делает их потоко-безопасными. Это, в первую очередь, применимо для всех переменных, для которых реализован механизм подсчета ссылок (строки, динамические массивы и т. д.). Рассомтрим следующий пример: Будем полагать, что вначале выполняться строки 8-9 первого потока, а затем одновременно выполняться строки 11 и 14. Тогда возможен следубщий сценарий:
1. Поток #1 выполняет строку 11. Он видит, что счетчик ссылок равен 2, поэтому уменьшает его на единицу (счетчик ссылок=1), но в это время прерывается.
2. Поток #2 видит, что счетчик ссылок равен единице, поэтому без заззрения совести освобождает строку, а указателю не нее присваивает nil.
3. Поток #1 пробуждается и доделывает свои манипуляции: выделяет новый фрагмент памяти, копирует в него старое содержимое строки. Но, строка уже указывает на освобожденный фрагмент памяти, что может привести к любимому всеми исключению Access violation.

Так вот, ключевое слово threadvar НЕ ДЕЛАЕТ такие пременные потоко-безопасными, и если вы хотите обращаться к переменным таких типов из разных потоков, то их работу необходимо синхронизировать.

Разработчики Delphi не рекомендуют использовать в качестве threadvar переменных указатели и процедурные типы.

Функции для работы с локальной памятью потока, предоставляемые Win32 API В Windows существует всего четыре простеньких функции для работы с локальной памятью потока. Но прежде чем их описывать, расскажем немного об основах TLS. При создании каждого потока Windows автоматически выделяет ему некоторый участок памяти, правда фиксированного размера, который содержит данные, доступные только этому потоку. Тут же храниться массив из TLS_MINIMUM_AVAIBLE указателей, который и использутся в качестве локальной памяти потока. Так что если поток хочет получить какое-то количество данных, то необходимо просто взять один из таких указателей (например нулевой), выделить ему столько памяти, сколько требуется и использовать его в качетве блока локальных переменных.

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

Илон Маск рекомендует:  Содержимое элемента

Кстати, вы можете потренировать свой навык набора на виртуальной клавиатуре с армянской раскладкой по ссылке
all-armenia.com/onlajn-klaviatura-(raskladka).html

А вот и обещанные четыре функции Windows: Функция возвращает номер свобожного элемента массива указателей, обнуляет указатель по этому индексу и меняет состояние этого элемента на занятое. Если свободных указателей больше нет, то функция вернет $FFFFFFFF. А эта функция просто делает элемент массива указателей с номером dwTlsIndex свободным. При этом не производится никакой попытки освободить указатель с указанным индексом — эта работа, которая лежит на Вас. Получаем элемент массива с индексом dwTlsIndex. В случае неудачного звершения функция вернет нулевой указатель. Но что если мы поместили по указанному индексу нулевой указатель? Тогда надо смотреть, что вернет GetLastError. Если он вернет NO_ERROR, значит действительно там храниться сейчас нулевой указатель. А иначе была ошибка. С другой стороны, если верить Джеффри Рихтеру, то в целях обеспечения скорости, в Windows не реализовано никакой проверки, свободен ли указанный элемент массива. Так что, во-первых, необходимо соблюдать осторожность при работе с TLS, а, во-вторых, необходимо поламать голову над тем, будет ли когда-нибудь такая проверка реализована в последующих версиях Windows, и стоит ли все эти проверки включать в свой код. Устанавливаем в элемент массива с индексом dwTlsIndex в значение lpTlsValue. Возвращает True при удачном завершении и False в случае ошибки. А дальше перечитайте описание функции TlsGetValue на предмет того, как реализована в Windows проверка ошибок

В заключение дадим ссылку на программу, которая выполняет все тоже, что и программа TLSDemo02.dpr, но с использованием функций Win32 API: TLSDemo03.dpr.

3. Как это реализовано в Delphi 5 Отметим, что все сказанное ниже относится к пятой версии Delphi и только к ней. Возможно, в прошлых версия это было не так, и в будущих это будет по-другому. Тогда зачем нужно это рассматривать? Ну, во-первых, изучение исходников полезно само по себе (ответ на вопрос, как это делают профессионалы). А во вторых, операторы меняются, а принципы остаются.

Все указанные ниже процедура располагаются в модуле SysInit, который можно найти в директории \Source\Rtl.

Одной из основных процедур Delphi, предназначенной для поддежки потоко-независимых переменых является _GetTLS. Эта процедура возвращает в регистре EAX указатель на блок threadvar переменных данного потока и вызывается непосредственно перед каждым обращением к потоко-независимой переменной. Вот ее реализация: Конечно, для новичка выглядит страшно, но. Обо всем по порядку.

1. Смотрим, эта часть кода выполняется в части dll или в части exe (переменная IsModuleLib). Если это exe, то мы возвращаем значение TlsGetValue(TlsIndex), только полученное весьма хитрым способом: TlsIndex — это индекс блока потоко-независимых переменных для данной dll или exe-модуля. Дело в том, что фактически по смещению tlsArray=$2C расположен указатель на массив указатлей, специфичных для данного потока упомянутый нами ранее массив из TLS_MINIMUM_AVAIBLE указателей), и мы вместо вызова TlsGetValue просто обращаемся к нужному нам индексу (строка 8). Ничего предосудительного в этом нет, компиляторы Microsoft используют ту же конструкию, хотя эта возможность и недокументирована.
2. Это dll. Смотрим, что нам вернула TlsGetValue(TlsIndex) и если это не нуль, то оное значение возвращается. Иначе.

3. Попытка проинициализировать блок TLS посредством вызова InitThreadTLS. И опять проверяем, что нам вернула TlsGetValue(TlsIndex). Если не нуль, то возвращаемся, а иначе.

4. Возвращаем значение хитрой переменной TlsBuffer (Случай dll, будет рассмотрен ниже).

Для случая части exe-модуля это все. Инициализацию сегмента TLS и установку переменной TlsIndex берет на себя непосредственно загрузчик программы в Delphi, которого мы и не видим.

В случае же dll инициализацию проводит вызов происходит при подсоединении к dll нового процесса. Рассмотрим этапы инициализации и деинициализации блока TLS для dll:

Инициализация TLS (случай DLL)

Сначала исходник, а болтовня потом: В обоих функциях присутствует загадочная переменная TlsLast. Компилятор всегда помещает эту переменную последней в сегменте переменных threadvar. Начало сегмента есть $00000000. Следовательно, адрес @TlsLast, используемый в обеих процедурах, есть не что иное, как суммарный размер всех потоко-независимых переменных. И @TlsLast=nil если в dll нет потоко-независимых переменных.

Что делает .InitProcessTLS? Смотрим по строкам.
1. Проверяет адрес переменной TlsLast на nil. Если это так, то никакой инициализации не требуется (строки 21-22).
2. Выполняет TlsAlloc и выполняет контроль на ошибку (строки 23-26).
3. Далее вызывается InitThreadTLS. (строка 27).
То есть, если выкинуль проверку на ошибки, процедура InitProcessTLS инициализирует переменную TlsIndex и выполняет вызов InitThreadTLS.

Что делает InitThreadTLS? Смотрим по строкам.
1. Проверяет адрес TlsLast на nil. Если это так, то выходим.
2. Проверяет на допустимость TlsIndex. (строки 7-8).
3. Производит выделение динамической памяти для блока переменных threadvar и присваевает указатель на него как в tlsBuffer, так и испотльзуя вызов TlsSetValue(TlsIndex, ). (строки 9-14). Заметим, что поскольку менеджер памяти может быть еще не проинициализирован системой, то динамическая память выделяется вызовом Win API.

Обобщаем. В случае dll при подключении к ней нового процесса происходит вызов InitProcessTLS, который выполняет инициализацию переменных TlsIndex и TlsBuffer. При обращении в dll к threadvar переменным, мы пытаемся получить указатель на переменные по индексу TlsIndex, и если попытка тщетна (например, обращение к переменной было произведено в потоке в первый раз), то выполняем вызов TlsInitThread, А потом перечитываем заново указатель TlsIndex. Самое интересное, что если в этом случае мы получаем nil, то берем значение из переменной TlsBuffer. Когда это может случиться и в чем смысл мне пока неизвестно, по крайней мере в отладчике я ни разу на эти строки не попадал.

Деинициализация TLS (случай DLL)

Илон Маск рекомендует:  Что такое код labs

Опять же, вначале рассмотрим исходник: Думаю, что сокрыто за этим исходником ясно и без комментариев. ExitThreadTLS освобождает динамическую память, связанную с конкретным потоком, а ExitProcessTLS освобождает элемент массива TLS с индексом TlsIndex.

Вопрос по Delphi threadvar

Когда объявлен threadvar , когда эта переменная будет инициализирована (объект создан)? Это происходит при первом присвоении var? Например:

Что произойдет, если я попытаюсь использовать этот var вне потока после того, как поток установил значение для var? Например:

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

Threadvars может быть завершена или не может быть завершена. Это зависит от того, насколько RTL получает уведомление о завершении потока. По этой причине, вероятно, лучше не хранить динамически распределенные типы (включенные строки) в threadvars. Вместо этого используйте переменную экземпляра объекта TThread для хранения данных, относящихся к потоку.

Вторая часть вашего вопроса — глупость. Он выполняет выполнение кода в потоке после того, как поток уже завершен. Нет такой вещи, как запуск кода «вне потока». Весь код работает в потоках. Каждая программа имеет хотя бы один поток.

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

Ваш вызов ShowMessage отобразит значение, принадлежащее текущему потоку, а не конец, который уже завершен.

Работает ли Delphi threadvar для Parallel.For?

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

Так можно ли использовать в Parallel.For как это?

1 ответ

Вы, конечно, можете использовать threadvar с кодом PPL. Внутренне код PPL находится поверх системных потоковых библиотек, поэтому threadvar работает так, как вы ожидаете.

Delphi threadvar question

When we have a threadvar declared, when this variable will be initialized (the object is created)? Does it occurs at the first assignment of the var? For example:

What happens if I try to use this var outside a thread after the thread has set the value for the var? For example:

3 Answers 3

The threadvars for a thread are initialized the first time their thread accesses any one of them. They are set to a default all-bits-zero value, which for strings is the empty string.

Threadvars may or may not be finalized. It depends on how much notice the RTL gets that a thread is terminating. For that reason, it’s probably best not to store any dynamically allocated types (strings included) in threadvars. Instead, use an instance variable of a TThread object to store thread-specific data.

The second part of your question is nonsense. It has you executing code on a thread after the thread has already terminated. There is no such thing as running code «outside a thread.» All code runs in threads. Every program has at least one thread.

Each thread has its own copy of a threadvar. No thread can read another thread’s copy, so once a thread terminates, all its threadvars are inaccessible.

Your ShowMessage call will display the value belonging to the current thread, not the thread that already terminated.

Обращение нескольких потоков к одному участку кода

12.02.2012, 03:40

Доступ к одному методу из двух потоков, критические секции
Вопрос такой: имеется объект MyObject класса TExample, содержащего метод DrawSomething(Cnv.

Обращение многих потоков к одному файлу
Как правильно синхронизировать такое: есть много потоков и один файл txt например. И в него.

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

Возможен ли одновременный доступ к одному ресурсу из нескольких потоков?
Возможен ли одновременный доступ к одному ресурсу из нескольких потоков? Допустим, есть.

Одновременное обращение к ApplicationContext в классе Singleton из нескольких потоков
Возникла проблема, в том что _context уже используется. ApplicationContext _context.

ThreadVar — Ключевое слово Delphi

Когда Exists по строке класс сохраняет последний найденный индекс и в будущем можно использовать перегруженный метод, чтобы прочитать его побыстрее.

05.01.2015, 11:36 #2
Человек_Борща
Посмотреть профиль
Найти ещё сообщения от Человек_Борща

Сабж. Я сделал вариант «нужно» lol. Хук на _IntfCopy, он вызывается при присваивании интерфейсов.

Хак в экзешнике, а классы только в dll-ках и там нормальный IntfCopy во избежание рекурсии. Но наверное можно сделать регистрацию и все такое.

Человек_Борща, выглядит уж слишком навороченным. Посмотрю, спасибо.

05.01.2015, 13:08 #3

я бы делал по принципу ThreadList.Lock, т.е. объект контейнер списка + минифабрика объектов доступа
List:=Data.GetList;
List.Exists(2);
или к Data вторым интерфейсом и в QueryInterface — подсовывать новый объект доступа

List[1]:=Data1 as ITEST;
Data2:=Data1;
List[2]:=Data2 as ITEST;
List[3]:=Data2 as ITEST;

можно на самого себя QueryInterface делать

05.01.2015, 18:26 #4
05.01.2015, 19:46 #5
14.11.2015, 23:32 #6
Мордохвост
Посмотреть профиль
Найти ещё сообщения от Мордохвост

FLastFound := TTls .Create;

Суть в том что переменная регистрируется при _AddRef и после никогда не пляшет с удалением, ни со сновадобавлением, просто живет пока есть ссылки. . короче я забыл пока мучался с TThreadLocalCounter. По обстановке надо решить нужно ли Dec(Recursion) или нет и затем соотвественно править _AddRef и _Release.

Вот так тестировал tlc(здесь tls — класс пока. ), 4 потока и бонус Put-Get-Delete где-нить в нажатии кнопки(MainThread вообщем), если будет raise — ну, не сложилось.

Использование threadvar в многопоточном приложении

14.11.2015, 23:33 #7
Цитата
В Delphi есть зарезервированное слово threadvar, что позволяет объявлять «глобальные» переменные, копия которых создается для каждого потока. Эта возможность используется нечасто, поскольку обычно удобнее размещать такие переменные в классе TThread, создавая, таким образом, один экземпляр переменной для каждого созданного потомка TThread.

Как и где правильно объявлять и использовать threadvar?

Решил ответить в тему. Думаю может будет многим интересно.

Так вот. Есть в Delphi такой способ определения переменных. Как и следует из цитаты, переменные определенные в разделе threadvar, имееют одну роковую особенность. Для каждого созданного в программе потока, автоматически создается собственная копия такой переменной.
Объявлять такие переменные можно в любом месте где разрешено объявлять глобальные переменные. Т.е. вне пределов процедур или функций. Это и понятно, ведь локальные переменные определяемые внутри процедур и функций размещаются в стеке, а он у каждого потока свой. Так что локальный threadvar, это нонсенс.

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

1. Если поток создается средствами API, без применения Delphi’йского объекта tThread, то локальные данные потока можно разместить только в стеке, т.е. локальные переменные. Но, такой способ предполагает что все процедуры и функции использующие эти данные должны быть вложенными по отношению к процедуре потока. Это сильно затрудняет написание программы.
Можно конечно и по другому. Создать в динамической памяти структуру (объект), и при вызове из процедуры потока других, не вложенных процедур, передавать в них указатель на эту структуру. Впротчем, именно так и происходит с классом tThread .
Вот здесь и приходит на помощь threadvar. Можно просто объявить переменную в разделе threadvar. Это будет обычная глобальная переменная, но т.к. для каждого потока она своя, то и не будет проблем при запуске нескольких потоков ее использующих.

2. Даже если мы адепты Delphi, и все наши потоки наследуются от tThread, то нам все равно может пригодиться threadvar. Продемонстрирую это на примере собственного использованя.
Есть у меня такой модуль awLog. Он экспортирует набор процедур предназначенных для ведения файла протокола. Естественно, процедуры этого модуля могут вызываться в контексте любого потока. Соответственно, есть программа которая обслуживает запросы удаленных клиентов — серверок такой. При подключении клиента к серверу, для его обслуживания создается индивидуальный поток. Тело потока для всех потоков одно, в смысле имеется единственный тип tThread, и для каждого пользователя создается собственный экземпляр этого типа. В процессе обслуживания, вызываются процедуры записи в протокол. Если не предпринять специальных действий, то в протоколе получится каша, в которой нет никакой возможности определить к какому клиенту относится та или иная запись.
Можно конечно добавить в процедуры протоколирования еще один параметр — имя клиента. И каждый раз, при вызове, передавать туда соответствующее значение. Но, модуль протоколирования у меня библиотечный, а по этому должен быть универсальным. Я его использую и в однопоточных приложениях. Выход есть. Я определил в модуле awLog threadvar-переменную UserName. Любой поток, может занести туда имя обслуживаемого в данный момент клиента. А все процедуры протоколирования, берут оттуда значение вставляемое в начало каждой строки протокола. Если же приложение однопоточное, то оно может в UserName ничего не заносить. Оно вообще может не знать о ее существовании.

3. Опять же есть библиотечный модуль для работы и Interbase-сервером через API. Его структура такова, что там есть необходимость использования глобальной переменной статуса завершения операции. Не хорошо конечно, но увы, без глобалов не всегда можно обойтись. По крайней мере не потеряв эффективность и наглядность программы.
Но, применение глобалов это обычно приговор: «не реентерабельно!». Т.е. использовать процедур общающиеся между собой через глобалы в мультипоточном приложении недопустимо!
Но, если эти глобалы сделать не var-переменными, а threadvar-переменными, то все будет в порядке.
Вот поэтому, в упомянутом мною библиотечном модуле, переменная статуса завершения операции определена в разделе threadvar.

Думаю, теперь стало ясно как и зачем нужны threadvar-переменные.

Но, это еще не все.
Казалось бы, найдено «петушиное слово». Теперь, мы все глобальные переменные описываем не как var, а как threadvar, и проблемма нереентерабельности библиотечных процедур решена навеки. Увы, не все так радужно. Дело в том, что механизм реализации threadvar-переменных таков, что обращение к ним обходится чувствительно дороже, нежели обращение к обычным глобальным переменным. Поэтому, не стоит их применять направо и налево.
В общем, я рекомендую не злоопутреблять ими, а применять их только в тех случаях, где без них просто не обойтись.

И еще. Это важно. Есть некоторые ограничения использования threadvar-переменных.
1. Эти переменные не могут иметь тип Pointer или тип procedure/function
2. threadvar-переменные типов имеющих так называемую copy-on-write логику, могут работать некорректно. Наиболее яркими представителями таких типов являются «длинные» строки и динамические массивы. Их использования для threadvar-переменных нежелательно. По крайней мере, так сказано в Help’е. Лично я с этим не согласен поскольку мне не понятно какие при этом возможны проблемы.

Работает ли Delphi threadvar для Parallel.For?

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

Так можно ли использовать в Parallel.For как это?

1 ответ

Вы, конечно, можете использовать threadvar с кодом PPL. Внутренне код PPL находится поверх системных потоковых библиотек, поэтому threadvar работает так, как вы ожидаете.

delphi — Работает ли Delphi threadvar для Parallel.For?

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

Так можно использовать в Parallel.For как это?

    4 1
  • 21 апр 2020 2020-04-21 13:35:41
  • Zhihua Lai

1 ответ

Вы можете использовать threadvar с кодом PPL. Внутренне код PPL стоит поверх системных библиотек потоков, поэтому threadvar работает так, как вы ожидали.

Илон Маск рекомендует:  Что такое код sendmessage
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL