LINUX.ORG.RU

TLS real use-cases

 


0

1

Всем привет!

Нашел вот такую статейку на просторах интернета. В связи с чем встал насущный вопрос - а где действительно используется thread local storage? Помнит ли кто-нибудь программы, где недостаточно было других стратегий управления многопоточностью, таких как Asynchronous message loop в boost.ASIO или Qt, или же fork-join-стратегия. Они же, насколько мне известно, нормально зарекомендовали свою масштабируемость и эффективность. Единственное место, где TLS действительно может быть нужен, на мой взгляд - это низкоуровневая прога навроде RTOS или Linux Kernel, так как всё находится в общем пространстве. Какие приложения в user-space пользуются TLS в хвост и в гриву и без этого никак?

★★

*.so, не?

anonymous
()

Какие приложения в user-space пользуются TLS в хвост и в гриву и без этого никак?

Интересно, а как по-вашему, должна работать strtok в многопоточном приложении? ;)

eao197 ★★★★★
()

Мне всегда казалось, что TLS - это самое нормальное, что есть, но сегодня я узнал, что есть и другие понятия. Мне кажется или вправду «асихронный цикл сообщений» не создаёт новых тредов, а обезпечивает параллельность в рамках одного треда? Я просто не работал с boost, а с Qt работал давно и вряд ли делал что-то с тредами. Насчёт fork-join прочитал кое-что, но ничего не понял. Неясно, что подлежит копированию. Например, если у нас есть глобальные переменные, стыдливо прикрытые синглтон-объектами, что с ними будет при fork? Если они дублируются, имеет ли это смысл? Если они остаются в одном экземпляре, будет ли это правильно работать или нужно ещё что-то дописывать? Я только понимаю про fork из Unix (хотя в жизни не применял) и он копирует всё состояние процесса. Вряд ли это можно назвать эффективным. TLS позволяет чётко сказать: это мы копируем, а это - нет. То, что мы сделали приватным для данного треда, может использоваться из этого треда без блокировок, т.к. это «частные» данные треда. Остальное нужно использовать аккуратно, например, защищая мьютексами и т.п. вещами.

den73 ★★★★★
()

По твоей же ссылке и написано:

Another common use of TLS is to implement low-overhead logging or tracing.

Stil ★★★★★
()

это просто глобальные переменные, удобно дающие «контекст» бизнес коду, «управление многопоточностью» - сервис код.

anonymous
()

Хороший пример - инструментирование кода для профайлинга.

Как написали выше - действительно можно обобщить до телеметрии сервисного кода. Больше - я реальных применений вроде и не видел.

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)

Обычные локальные переменные на стеке потока - уже thread local, без оверхеда и костылей, я надеюсь это понятно. Поэтому в обычном коде TLS не нужен. Так что единственное где он нужен - это костыли для того чтобы прозрачно сделать легаси API с паталогически глобальным состоянием пригодным для использования в многопоточной среде. Самый очевидный пример - errno.

slovazap ★★★★★
()
Ответ на: комментарий от den73

Мне кажется или вправду «асихронный цикл сообщений» не создаёт новых тредов, а обезпечивает параллельность в рамках одного треда?

Он не обеспечивает параллельности, он обеспечивает асинхронную обработку. Параллельность делается отдельно. В частности boost.asio работал (до последней версии, сейчас че-то поменяли в API) примерно так: создавался пул тредов, в каждом треде запускался инстанс io_service, который обрабатывает входящие задачи как только освобождается.

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

Насчёт fork-join прочитал кое-что, но ничего не понял. Неясно, что подлежит копированию. Например, если у нас есть глобальные переменные, стыдливо прикрытые синглтон-объектами, что с ними будет при fork? Если они дублируются, имеет ли это смысл? Если они остаются в одном экземпляре, будет ли это правильно работать или нужно ещё что-то дописывать?

Там другая идеология. Положим, есть у тебя N-ядерный проц и гигабайты независимых данных. Например, длинная таблица записей в БД. Ясно, что здесь каждая запись не зависит от других. Ну тогда можно разделить всю таблицу на N независимых частей, и каждому из N тредов давать указатель на начало данных, которые нужно обработать, и количество этих строчек для обработки. Ясно, что в этой простой, но частой ситуации даже мьютексы не понадобятся, так что все потоки будут работать независимо друг от друга. И копирования никакого нет. Когда данные обработаны все треды джойнятся. Если приходит новая таблица для обработки - снова создаем треды и действуем снова по тому же сценарию. Так как копирования данных нет, то и TLS тут снова не нужен.

aido ★★
() автор топика
Ответ на: комментарий от Stil

да. ну вот только это еще забыл в посте упомянуть. Хотя кто это сейчас в 2к18 году пишет при наличии уже готовых spdlog, boost.log и других?

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

aido ★★
() автор топика
Ответ на: комментарий от slovazap

Поэтому в обычном коде TLS не нужен.

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

Deleted
()
Ответ на: комментарий от slovazap

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

Понятно, что у тебя какое-то свое, личное определение thread local.

tailgunner ★★★★★
()
Ответ на: комментарий от eao197

Встречный вопрос - стратегически, а зачем запускать strtok одновременно в разных тредах? писать кастомные парсеры входных строковых данных одновременно из разных файлов? Не проще ли об этом забыть и использовать готовые реализации парсеров типа json, и т.д.

Но, спасибо за наводящий вопрос: выявились ещё все библиотеки парсеров.

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

aido ★★
() автор топика
Ответ на: комментарий от aido

Но, спасибо за наводящий вопрос: выявились ещё все библиотеки парсеров.

Вы не поняли. Речь про API, который достался со времен строго однопоточных приложений. Кроме strtok выше уже упоминали errno.

Кроме того, в ваших рассуждениях есть одна большая ошибка: вы почему-то решили, что TLS должен использоваться широко и повсеместно. Это не так. Поэтому-то TLS используется в тех редких ситуациях, когда другие подходы слишком уж трудоемки, запутаны и/или неэффективны.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Я просто в одном проекте увидел повсеместное использование TLS и удивился, почему я о нем не знаю после 4 лет промышленного программирования.

aido ★★
() автор топика
Ответ на: комментарий от aido

Он не обеспечивает параллельности, он обеспечивает асинхронную обработку.

Спасибо!

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

Но переход к многопоточности здесь не может быть бездумным. При однопоточности каждый обработчик выполняется в неявной критической секции, поскольку очередь сообщений не трогается до его возврата (не считая аномальных случаев типа запуска вложенного цикла сообщений, для GUI это будет модальная форма, к примеру). Когда возникает пул тредов, эта критическая секция аннигилирует. Представь себе, 10 фрезеровщиков, но станок один. Если наивно попытаться так сделать, работать ничего не будет.

Насчёт fork-join я не понял твой пример, поскольку для СУБД все записи в одной таблице всегда зависят друг от друга на самом деле, если речь идёт о записи в БД.

Но допустим, потоки только читают данные из БД и не пишут их, и вычисляют, допустим, среднее и дисперсию по своей порции, возвращают два числа. Здесь получается, что данные не копируются (глобальны), а состояние процесса перед началом работы достаточно тривиально (имя файла и диапазон строк) и поэтому его дёшево копировать. Так?

den73 ★★★★★
()
Ответ на: комментарий от den73

Представь себе, 10 фрезеровщиков, но станок один. Если наивно попытаться так сделать, работать ничего не будет.

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

Но допустим, потоки только читают данные из БД и не пишут их, и вычисляют, допустим, среднее и дисперсию по своей порции, возвращают два числа. Здесь получается, что данные не копируются (глобальны), а состояние процесса перед началом работы достаточно тривиально (имя файла и диапазон строк) и поэтому его дёшево копировать. Так?

Да. Типичная задача для любой расчетки из физмат-области. Обработка данных с датчиков часто (но не всегда) тоже укладывается в форк-джоин. Не всегда - потому, что порой нужна более умная постобработка данных в режиме реалтайма. Тут действительно пригождаются мьютексы.

aido ★★
() автор топика
Ответ на: комментарий от aido

Говоря «станок», я имел в виду как раз не процессор, а некие общие ресурсы, будь то stdout, гуй и т.п. Представь себе обработчик событий такой:

по некоему-событию
  пиши "\nhel" в stdout
  пиши "l" в stdout
  пиши "o" в stdout
  заверши-вывод
Если нет worker-тредов, это будет работать. Если два таких обработчика запустятся в разных worker тредах, то получится hell. Т.е. однопоточную программу нельзя просто так перевести на параллельность на базе worker тредов.

den73 ★★★★★
()
Ответ на: комментарий от den73

Ах, вы об этом. Да, встречал эту проблему, когда писал логгер тредов, в boost.asio. Решил довольно просто - либо логгировал в разные файлы в соответствии с каждым тредом (ну раз треды все же разные и занимаются разными вещами, то и логгировать скорее всего надо в свои области, не?), либо блочил на запись мьютексами выходной слив (в spdlog это называлось sink - слив), если требовалось логгировать в один файл. Такое просматривать без грепа неудобно, но иначе никак (или TLS бы тут помог?).

aido ★★
() автор топика

В связи с чем встал насущный вопрос - а где действительно используется thread local storage?

Почти везде. Кроме шуток: глубоко внутри errno как раз tls-переменная.

kawaii_neko ★★★★
()
Последнее исправление: kawaii_neko (всего исправлений: 1)
Ответ на: комментарий от slovazap

И как это относится к TLS?

Можно для каждого потока держать в TLS указатель на свой буфер. Как результат: не нужно выделять память при каждом вызове функции, и несколько потоков не мешают друг другу.

В общем представь сферическую в вакууме реализацию функции void log(const char *format, ...), которая должна отформатировать строку перед отправкой дальше.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от aido

Решил довольно просто - либо логгировал в разные файлы

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

den73 ★★★★★
()

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

MKuznetsov ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.