Каковы преимущества локального хранилища на уровне экземпляра? - PullRequest
16 голосов
/ 04 февраля 2010

Этот вопрос заставил меня задуматься о локальном потоке хранения в высокоуровневых средах разработки, таких как 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();
}

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

Ответы [ 6 ]

11 голосов
/ 05 февраля 2010

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

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

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

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

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

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

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

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

6 голосов
/ 04 февраля 2010

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

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

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

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

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

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

4 голосов
/ 04 февраля 2010

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

3 голосов
/ 04 февраля 2010

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

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

1 голос
/ 10 января 2013

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

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 из любого места, но к разным копиям.

1 голос
/ 15 июля 2010
...