На каких платформах ограничено локальное хранилище потоков и сколько доступно? - PullRequest
15 голосов
/ 22 сентября 2009

Недавно я узнал, что локальное хранилище потоков ограничено на некоторых платформах. Например, документы для библиотеки C ++ boost :: thread читаются:

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

Я искал, чтобы попытаться выяснить ограничения для разных платформ, но мне не удалось найти достоверную таблицу. Это важный вопрос, если вы пишете кроссплатформенное приложение, использующее TLS. Linux была единственной платформой, для которой я нашел информацию, в виде патча, который Инго Монар отправил в 2002 году в список ядра, добавив поддержку TLS, где он упоминает: «Число областей TLS не ограничено, и нет дополнительные затраты на выделение ресурсов, связанные с поддержкой TLS. "Что, если все еще верно в 2009 году (правда?), довольно изящно.

А как же Linux сегодня? OS X? Окна? Solaris? Встроенные ОС? Для ОС, работающих на нескольких архитектурах, она различается в зависимости от архитектуры?

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

Ответы [ 6 ]

11 голосов
/ 22 сентября 2009

В 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 и поместите все необходимое в структуру данных, подвешенную за этим указателем.

3 голосов
/ 22 сентября 2009

Я использовал TLS только в Windows, и между версиями есть небольшая разница в том, сколько можно использовать: http://msdn.microsoft.com/en-us/library/ms686749(VS.85).aspx

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

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

На Mac я знаю Хранилище для конкретных задач в Многопроцессорных сервисах API:

MPAllocateTaskStorageIndex
MPDeallocateTaskStorageIndex
MPGetTaskStorageValue
MPSetTaskStorageValue

Это выглядит очень похоже на локальное хранилище потока Windows.

Я не уверен, рекомендуется ли этот API в настоящее время для локального хранилища потоков на Mac. Возможно, есть что-то более новое.

0 голосов
/ 22 сентября 2009

Я использую простой класс шаблона для обеспечения локального хранилища потока. Это просто оборачивает 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(). Для вызова этой функции можно использовать глобальную функцию, которая выведет параметры шаблона.

0 голосов
/ 22 сентября 2009

Declspec локального хранилища потоков в Windows ограничивает использование его только для статических переменных, что означает, что вам не повезло, если вы хотите использовать его более творчески.

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

С другой стороны, API-интерфейс pthread для локального хранилища хорошо продуман и гибок.

0 голосов
/ 22 сентября 2009

Возможно, в документации надстройки просто говорится об общем настраиваемом пределе, а не обязательно о каком-то жестком ограничении платформы. В Linux команда ulimit ограничивает ресурсы, которые могут иметь процессы (количество потоков, размер стека, память и множество других вещей). Это косвенно повлияет на локальное хранилище вашего потока. В моей системе, похоже, нет записи в ulimit, относящейся к локальному хранилищу потоков. Другие платформы могут иметь способ указать это самостоятельно. Кроме того, я думаю, что во многих многопроцессорных системах локальное хранилище потоков будет в памяти, выделенной для этого ЦП, поэтому вы можете столкнуться с ограничениями физической памяти задолго до того, как система в целом исчерпает свою память. Я бы предположил, что в этой ситуации есть какое-то резервное поведение, чтобы найти данные в основной памяти, но я не знаю. Как вы можете сказать, я многое предполагаю. Надеюсь, это все еще ведет вас в правильном направлении ...

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