Подтвердить что ты не робот

Каковы преимущества локального хранилища потоков на уровне экземпляра?

Этот вопрос заставлял меня задаваться вопросом о потоковом локальном хранилище в высоком -уровневые разработки, такие как Java и .NET.

Java имеет ThreadLocal<T> класс (и, возможно, другие конструкции), в то время как .NET имеет слоты данных, и вскоре ThreadLocal<T> собственный. (Он также имеет ThreadStaticAttribute, но меня особенно интересует поточно-локальное хранилище данных участника.) Большинство других современных сред разработки предоставить один или несколько механизмов для него, будь то на уровне языка или структуры.

Какие проблемы решает проблема локального хранилища потоков или какие преимущества обеспечивает поточно-локальное хранилище над стандартной объектно-ориентированной идиомой создания отдельных экземпляров объектов для хранения локальных данных потока? Другими словами, как это:

// Thread local storage approach - start 200 threads using the same object
// Each thread creates a copy of any thread-local data
ThreadLocalInstance instance = new ThreadLocalInstance();
for(int i=0; i < 200; i++) {
    ThreadStart threadStart = new ThreadStart(instance.DoSomething);
    new Thread(threadStart).Start();
}

Превосходно?

// Normal oo approach, create 200 objects, start a new thread on each
for(int i=0; i < 200; i++) {
    StandardInstance standardInstance = new StandardInstance();
    ThreadStart threadStart = new ThreadStart(standardInstance.DoSomething);      
    new Thread(threadStart).Start();
}

Я вижу, что использование одного объекта с локальным хранилищем потоков может быть немного более экономичным и требует меньше ресурсов процессора из-за меньшего количества распределений (и конструкций). Существуют ли другие преимущества?

4b9b3361

Ответ 1

Какие проблемы решает проблема нитей-локального хранилища или какие преимущества предоставляет поточно-локальное хранилище по стандартной объектно-ориентированной идиоме создания отдельных экземпляров объектов для размещения локальных данных потока?

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

Что касается преимущества по сравнению с вашим примером - если вы создаете один поток, мало пользы от использования локального хранилища потоков при передаче в экземпляре. ThreadLocal<T> и подобные конструкции становятся невероятно ценными, однако, при работе (прямо или косвенно) с ThreadPool.

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

Однако попытка совместного использования этого кеша через потоки проблематична. Для этого нам нужно будет синхронизировать доступ к нему, а также добавить дополнительные проверки внутри нашего класса, чтобы сделать их потокобезопасными.

Вместо этого я переписал алгоритм, чтобы каждый поток поддерживал свой собственный частный кеш в ThreadLocal<T>. Это позволяет каждому потоку поддерживать собственный частный кеш. Поскольку схема разбиения, используемая TPL, имеет тенденцию удерживать блоки элементов вместе, каждый локальный кеш потока имеет тенденцию содержать требуемые значения.

Это устранило проблемы синхронизации, но также позволило нам сохранить наше кэширование на месте. В этой ситуации общая выгода была довольно большой.

Для более конкретного примера взгляните на это сообщение в блоге, которое я написал в перегрузку ForEach, которая поддерживает локальное состояние (и методы Parallel.For<TLocal> тоже). Таким образом, локальное состояние сохраняется отдельно для каждого потока, чтобы избежать блокировки.

Ответ 2

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

Другим хорошим примером является System.Random в .NET. Общеизвестно, что вы не должны создавать новый экземпляр каждый раз, когда хотите использовать Random, поэтому некоторые люди создают один экземпляр и помещают его в статическую переменную... но это неудобно, потому что Random не является потокобезопасный. Вместо этого вам действительно нужен один экземпляр для потока, который высевается соответствующим образом. ThreadLocal<T> отлично работает для этого.

Аналогичными примерами являются культура, связанная с потоком, или контекст безопасности.

В общем, это случай, когда вы не хотите проходить слишком много контекста по всему месту. Вы можете сделать каждый вызов метода "RandomContext" или "LogContext", но это будет мешать вашей чистоте API, и цепочка будет нарушена, если вам когда-либо приходилось звонить в другой API, который перезвонил бы ваш через виртуальный метод или что-то подобное.

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

Я бы сказал, что в большинстве случаев вы можете избавиться от статического - но иногда вам может понадобиться информация об одном экземпляре, для каждого потока. Опять же, стоит использовать ваше мнение, чтобы узнать, где это полезно.

Ответ 3

Он помогает передавать значение в стек. Это удобно, когда вам нужно значение вниз по стеку вызовов, но нет способа (или преимущества) передать это значение в нужное место в качестве параметра для метода. Приведенный выше пример хранения текущего HttpRequest в ThreaLocal является хорошим примером этого: альтернативой было бы передать HttpRequest в качестве параметра вниз по стеку туда, где он понадобился.

Ответ 4

В Java локальное хранилище потоков может быть полезно в веб-приложении, где один запрос обычно обрабатывается данным потоком. Возьмите Spring Безопасность, например, фильтр безопасности выполнит проверку подлинности, а затем сохранит учетные данные пользователей в локальной переменной потока.

Это позволяет фактическому коду обработки запроса иметь доступ к текущей информации о запросе/аутентификации пользователей без необходимости вносить что-либо еще в код.

Ответ 6

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

function startComputingA(other args) {
  global_v = create // declared locally
  call A2(other args, global_v)
  call A3(other args, global_v)

function A2(other args, global_v) {
  call A3(other args, global_v)

function A3(other args, global_v) {
  call A4(other args, global_v)

Все ваши функции должны объявлять аргумент global_v. Это отстой. У вас есть глобальная область для хранения глобальных переменных и маршрутизация "виртуально" в каждую процедуру

variable global_v;
function A() { // use global_v and call B() }
function B() { // use global_v and call C() }

Тем не менее, может случиться, что другой поток начинает выполнять некоторые из этих функций. Это повредит вашу глобальную переменную. Таким образом, вы хотите, чтобы переменная отображалась глобально для всех подпрограмм, но не между потоками. Вы хотите, чтобы каждый поток имел отдельную копию global_v. Здесь, когда местное хранилище незаменимо! Вы объявляете global_v как локальную переменную потока. Таким образом, любые потоки могут получить доступ к global_v из любого места, но в разных его копиях.