15

最近、一部のプラットフォームでスレッドローカルストレージが制限されていることに気づきました。たとえば、C++ライブラリのブースト::スレッドのドキュメントは次のとおりです。

「注:作成できるスレッド固有のストレージオブジェクトの数には実装固有の制限があり、この制限は小さい場合があります。」

さまざまなプラットフォームの制限を見つけようと探していましたが、信頼できるテーブルを見つけることができませんでした。TLSを使用するクロスプラットフォームアプリを作成している場合、これは重要な質問です。Linuxは、2002年にIngo MonarがTLSサポートを追加するカーネルリストに送信したパッチの形で情報を見つけた唯一のプラットフォームでした。彼は次のように述べています。「TLS領域の数は無制限であり、関連する追加の割り当てオーバーヘッドはありません。 TLSサポート付き。」2009年にまだ真実である場合(そうですか?)、かなり気の利いたものです。

しかし、今日のLinuxはどうですか?OS X?ウィンドウズ?Solaris?組み込みOS?複数のアーキテクチャで実行されるOSの場合、アーキテクチャによって異なりますか?

編集:なぜ制限があるのか​​知りたい場合は、スレッドローカルストレージ用のスペースが事前に割り当てられることを考慮してください。そのため、すべてのスレッドでそのコストを支払うことになります。たくさんのスレッドに直面して少量でも問題になる可能性があります。

4

6 に答える 6

13

Linux で__threadTLS データを使用している場合、このデータはgs(x86 の場合) またはfs(x86-64 の場合) セグメント記述子によって参照される通常の RAM として単純に割り当てられるため、使用可能なアドレス空間によって制限が設定されます。場合によっては、動的にロードされたライブラリによって使用される TLS データの割り当てが、その TLS データを使用しないスレッドでは省略される可能性があることに注意してください。

pthread_key_createただし、フレンドによって割り当てられる TLSはPTHREAD_KEYS_MAXスロットに限定されます (これは、準拠するすべての pthread 実装に適用されます)。

Linux での TLS 実装の詳細については、「スレッド ローカル ストレージの ELF 処理」および「Linux のネイティブ POSIX スレッド ライブラリ」を参照してください。

とはいえ、移植性が必要な場合は、TLS の使用を最小限に抑えるのが最善の策です。TLS に単一のポインターを配置し、必要なものすべてをそのポインターにぶら下がったデータ構造に配置します。

于 2009-09-22T16:11:48.713 に答える
4

私は Windows でのみ TLS を使用しましたが、バージョン間で使用できる量にわずかな違いがあります: http://msdn.microsoft.com/en-us/library/ms686749(VS.85).aspx

あなたのコードは、スレッドをサポートするオペレーティング システムのみをターゲットにしていると思います。過去に、スレッドをサポートしない組み込みおよびデスクトップ OS で作業したことがあるので、TLS をサポートしません。

于 2009-09-22T15:22:28.623 に答える
1

Mac では、マルチプロセッシング サービスAPIのタスク固有のストレージを知っています。

MPAllocateTaskStorageIndex
MPDeallocateTaskStorageIndex
MPGetTaskStorageValue
MPSetTaskStorageValue

これは、Windows スレッド ローカル ストレージと非常によく似ています。

この API が現在 Mac のスレッド ローカル ストレージに推奨されているかどうかはわかりません。もしかしたら、もっと新しいものがあるかもしれません。

于 2010-02-05T19:42:53.310 に答える
0

単純なテンプレートクラスを使用して、スレッドローカルストレージを提供します。これは単にstd::mapとクリティカルセクションをラップします。これにより、プラットフォーム固有のスレッドローカルの問題が発生することはありません。唯一のプラットフォーム要件は、現在のスレッドIDを整数で取得することです。ネイティブスレッドローカルストレージよりも少し遅いかもしれませんが、任意のデータ型を保存できます。

以下は私のコードの縮小版です。コードを簡略化するために、デフォルト値のロジックを削除しました。任意のデータ型を格納できるため、インクリメント演算子とデクリメント演算子は、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()です。グローバル関数を使用してこれを呼び出すことができ、テンプレートパラメータを推測します。

于 2009-09-22T16:51:41.113 に答える
0

ブーストのドキュメントは、単に一般的な構成可能な制限について話しているだけであり、必ずしもプラットフォームのハード制限について話しているわけではない可能性があります。Linux では、ulimitコマンドは、プロセスが持つことができるリソースを制限します (スレッドの数、スタックサイズ、メモリ、およびその他のものの束)。これは間接的にスレッド ローカル ストレージに影響します。私のシステムでは、スレッド ローカル ストレージに固有の ulimit にエントリがないようです。他のプラットフォームには、それを独自に指定する方法がある場合があります。また、多くのマルチプロセッサ システムでは、スレッド ローカル ストレージはその CPU 専用のメモリにあるため、システム全体のメモリが使い果たされるずっと前に、物理メモリの限界に達する可能性があります。そのような状況でメインメモリにデータを配置するための何らかのフォールバック動作があると思いますが、わかりません。お察しのとおり、私は多くのことを推測しています。うまくいけば、それはまだあなたを正しい方向に導きます...

于 2009-09-22T14:59:42.750 に答える
0

Windows のスレッド ローカル ストレージ declspec では、静的変数のみに使用するように制限されています。

Windows には低レベルの API がありますが、セマンティクスが壊れているため、初期化が非常に厄介です。変数がスレッドによって既に認識されているかどうかがわからないため、変数を使用するときに明示的に初期化する必要があります。スレッドを作成します。

一方、スレッド ローカル ストレージ用の pthread API は、よく考え抜かれた柔軟性があります。

于 2009-09-22T16:30:15.507 に答える