На каких платформах ограничено локальное хранилище потоков и сколько доступно?
Недавно мне стало известно, что локальное хранилище потоков ограничено на некоторых платформах. Например, документы для библиотеки С++ boost:: thread read:
"Примечание. Ограничение реализации зависит от количества объектов хранения конкретных потоков, которые могут быть созданы, и этот предел может быть небольшим."
Я искал попытку выяснить пределы для разных платформ, но мне не удалось найти авторитетную таблицу. Это важный вопрос, если вы пишете кроссплатформенное приложение, использующее TLS. Linux была единственной платформой, на которой я нашел информацию, в виде патча Ingo Monar, отправленного в 2002 году в список ядра, добавляющий поддержку TLS, где он упоминает: "Количество TLS-областей неограничено, и нет
дополнительные расходы на распределение, связанные с поддержкой TLS ". Что, если все еще верно в 2009 году (это так?), довольно изящно.
Но как насчет Linux сегодня? OS X? Окна? Solaris? Встроенные ОС? Для ОС, работающей на нескольких архитектурах, зависит ли она от разных архитектур?
Изменить: Если вам интересно, почему может быть предел, подумайте, что пространство для локального хранилища потоков будет предварительно распределено, поэтому вы будете платить за него в каждом отдельном потоке. Даже небольшая сумма перед лицом множества потоков может быть проблемой.
Ответы
Ответ 1
В Linux, если вы используете __thread
TLS-данные, единственное ограничение устанавливается вашим доступным адресным пространством, так как эти данные просто распределяются как обычная оперативная память, на которую ссылаются gs
(на x86) или fs
(на x86-64) дескрипторы сегментов. Обратите внимание, что в некоторых случаях распределение данных TLS, используемых динамически загружаемыми библиотеками, может быть отменено в потоках, которые не используют данные TLS.
TLS, назначенные pthread_key_create
, а друзья, однако, ограничены слотами PTHREAD_KEYS_MAX
(это относится ко всем соответствующим реализациям pthreads).
Для получения дополнительной информации о реализации TLS в Linux см. Обработка ELF для хранилища на основе темы и Собственная библиотека потоков POSIX для Linux.
Тем не менее, если вам нужна переносимость, лучше всего свести к минимуму использование TLS - поместите один указатель в TLS и поместите все, что вам нужно, в структуру данных, отвисшую от этого указателя.
Ответ 2
Я использовал TLS только в Windows, и есть небольшие различия между версиями в том, сколько можно использовать:
http://msdn.microsoft.com/en-us/library/ms686749(VS.85).aspx
Я предполагаю, что ваш код ориентирован только на операционные системы, поддерживающие потоки - в прошлом я работал со встроенными и настольными ОС, которые не поддерживают потоки, поэтому не поддерживают TLS.
Ответ 3
На Mac я знаю Task-Specific Storage в Многопроцессорные сервисы API:
MPAllocateTaskStorageIndex
MPDeallocateTaskStorageIndex
MPGetTaskStorageValue
MPSetTaskStorageValue
Это похоже на локальное хранилище Windows.
Я не уверен, что этот API в настоящее время рекомендуется для локального хранилища потоков на Mac. Возможно, что-то новое.
Ответ 4
Возможно, документация по ускорению просто говорит об общем настраиваемом лимите, не обязательно о каком-то жестком пределе платформы. В Linux ограничение ulimit ограничений ресурсов может иметь (количество потоков, размер стека, память и множество других вещей), Это косвенно повлияет на локальное хранилище ваших потоков. В моей системе, похоже, нет записи в ulimit, специфичной для локального хранилища потоков. У других платформ есть возможность самостоятельно указать это. Кроме того, я думаю, что во многих многопроцессорных системах локальное хранилище потоков будет в памяти, предназначенном для этого процессора, поэтому вы можете столкнуться с ограничениями физической памяти задолго до того, как система в целом исчерпает свою память. Я бы предположил, что в этой ситуации есть какое-то резервное поведение, чтобы найти данные в основной памяти, но я не знаю. Как вы можете сказать, я многого хочу. Надеюсь, это все равно приведет вас в правильном направлении...
Ответ 5
Локальное хранилище данных declspec в Windows ограничивает его использование только для статических переменных, что означает, что вам не повезло, если вы хотите использовать его более творчески.
В Windows существует низкоуровневый API, но он имеет сломанную семантику, которая очень неудобно инициализировать: вы не можете определить, была ли переменная уже замечена вашим потоком, поэтому вам нужно явно инициализировать это когда вы создаете поток.
С другой стороны, API-интерфейс pthread для локального хранилища потоков хорошо продуман и гибкий.
Ответ 6
Я использую простой класс шаблонов для обеспечения локального хранилища потоков. Это просто обертывает std::map
и критический раздел. Тогда это не страдает от каких-либо локальных проблем, связанных с конкретной конкретной платформой, единственное требование к платформе - получить текущий идентификатор потока, как в целочисленном. Это может быть немного медленнее, чем локальное хранилище собственного потока, но оно может хранить любой тип данных.
Ниже приведена сокращенная версия моего кода. Я удалил логику значений по умолчанию, чтобы упростить код. Поскольку он может хранить любой тип данных, операторы приращения и уменьшения доступны только в том случае, если T
поддерживает их. Критический раздел необходим только для защиты поиска и вставки на карту. После возврата ссылки безопасно использовать незащищенный, так как только текущий поток будет использовать это значение.
template <class T>
class ThreadLocal
{
public:
operator T()
{
return value();
}
T & operator++()
{
return ++value();
}
T operator++(int)
{
return value()++;
}
T & operator--()
{
return --value();
}
T operator--(int)
{
return value()--;
}
T & operator=(const T& v)
{
return (value() = v);
}
private:
T & value()
{
LockGuard<CriticalSection> lock(m_cs);
return m_threadMap[Thread::getThreadID()];
}
CriticalSection m_cs;
std::map<int, T> m_threadMap;
};
Чтобы использовать этот класс, я обычно объявляю статический член внутри класса, например
class DBConnection {
DBConnection() {
++m_connectionCount;
}
~DBConnection() {
--m_connectionCount;
}
// ...
static ThreadLocal<unsigned int> m_connectionCount;
};
ThreadLocal<unsigned int> DBConnection::m_connectionCount
Это может быть не идеально для каждой ситуации, но оно покрывает мою потребность, и я могу легко добавить любые функции, которые отсутствуют, когда я их обнаруживаю.
bdonlan правильный, этот пример не очищается после выхода из потока. Однако это очень просто добавить ручную очистку.
template <class T>
class ThreadLocal
{
public:
static void cleanup(ThreadLocal<T> & tl)
{
LockGuard<CriticalSection> lock(m_cs);
tl.m_threadMap.erase(Thread::getThreadID());
}
class AutoCleanup {
public:
AutoCleanup(ThreadLocal<T> & tl) : m_tl(tl) {}
~AutoCleanup() {
cleanup(m_tl);
}
private:
ThreadLocal<T> m_tl
}
// ...
}
Затем поток, который его знает, явно использует ThreadLocal
, может использовать ThreadLocal::AutoCleanup
в своей основной функции для очистки переменной.
Или в случае DBConnection
~DBConnection() {
if (--m_connectionCount == 0)
ThreadLocal<int>::cleanup(m_connectionCount);
}
Метод cleanup()
является статическим, чтобы не мешать operator T()
. Для вызова этой функции можно использовать глобальную функцию, которая выводит параметры шаблона.