簡體   English   中英

C ++異常是否足以實現線程本地存儲?

[英]Are C++ exceptions sufficient to implement thread-local storage?

我正在評論一個線程本地存儲很好的答案 ,並回憶起關於異常的另一個信息性討論

throw塊中執行環境的唯一特殊之處在於rethrow引用了異常對象。

將兩個和兩個放在一起,不會在其主函數的函數catch塊中執行整個線程,並將其與線程本地存儲一起使用?

雖然很慢,但似乎工作正常。 這是小說還是很有特色? 還有另一種解決問題的方法嗎? 我最初的前提是否正確? get_thread會在您的平台上產生什么樣的開銷? 優化的潛力是什么?

#include <iostream>
#include <pthread.h>
using namespace std;

struct thlocal {
    string name;
    thlocal( string const &n ) : name(n) {}
};

struct thread_exception_base {
    thlocal &th;
    thread_exception_base( thlocal &in_th ) : th( in_th ) {}
    thread_exception_base( thread_exception_base const &in ) : th( in.th ) {}
};

thlocal &get_thread() throw() {
    try {
        throw;
    } catch( thread_exception_base &local ) {
        return local.th;
    }
}

void print_thread() {
    cerr << get_thread().name << endl;
}

void *kid( void *local_v ) try {
    thlocal &local = * static_cast< thlocal * >( local_v );
    throw thread_exception_base( local );
} catch( thread_exception_base & ) {
    print_thread();

    return NULL;
}

int main() {
    thlocal local( "main" );
    try {
        throw thread_exception_base( local );
    } catch( thread_exception_base & ) {
        print_thread();

        pthread_t th;
        thlocal kid_local( "kid" );
        pthread_create( &th, NULL, &kid, &kid_local );
        pthread_join( th, NULL );

        print_thread();
    }

    return 0;
}

這確實需要定義從thread_exception_base派生的新異常類,用get_thread()初始化基類,但總的來說這並不像一個無效的失眠周日早晨......

編輯:看起來GCC在get_threadpthread_getspecific進行了三次調用。 編輯:和堆棧,環境和可執行格式的很多討厭的內省,以找到我在第一次演練中錯過的catch塊。 這看起來高度依賴於平台,因為GCC從操作系統調用了一些libunwind 開銷大約4000次循環。 我想它也必須遍歷類層次結構,但可以控制它。

在這個問題的俏皮精神中,我提供了這個可怕的噩夢創作:

class tls
{
    void push(void *ptr)
    {
        // allocate a string to store the hex ptr 
        // and the hex of its own address
        char *str = new char[100];
        sprintf(str, " |%x|%x", ptr, str);
        strtok(str, "|");
    }

    template <class Ptr>
    Ptr *next()
    {
        // retrieve the next pointer token
        return reinterpret_cast<Ptr *>(strtoul(strtok(0, "|"), 0, 16));
    }

    void *pop()
    {
        // retrieve (and forget) a previously stored pointer
        void *ptr = next<void>();
        delete[] next<char>();
        return ptr;
    }

    // private constructor/destructor
    tls() { push(0); }
    ~tls() { pop(); }

public:
    static tls &singleton()
    {
        static tls i;
        return i;
    }

    void *set(void *ptr)
    {
        void *old = pop();
        push(ptr);
        return old;
    }

    void *get()
    {
        // forget and restore on each access
        void *ptr = pop();
        push(ptr);
        return ptr;
    }
};

利用根據C ++標准的事實, strtok其第一個參數,以便后續調用可以傳遞0以從同一個字符串中檢索更多的標記,因此在線程感知實現中它必須使用TLS。

example *e = new example;

tls::singleton().set(e);

example *e2 = reinterpret_cast<example *>(tls::singleton().get());

因此,只要strtok沒有以預期的方式在程序中的任何其他位置使用,我們就有另一個備用TLS插槽。

我想你在這里做點什么。 即使除了明確使用線程之外,這甚至可能是一種將數據轉換為不接受用戶“狀態”變量的回調的可移植方式。

所以聽起來你已經在你的主題中回答了這個問題:是的。

void *kid( void *local_v ) try {
    thlocal &local = * static_cast< thlocal * >( local_v );
    throw local;
} catch( thlocal & ) {
    print_thread();

    return NULL;
}

==

void *kid (void *local_v ) { print_thread(local_v); }

我可能在這里遺漏了一些東西,但它不是線程本地存儲,只是不必要地復雜的參數傳遞。 每個線程的參數都不同,因為它傳遞給pthread_create,而不是因為任何異常雜亂。


事實證明,我確實錯過了GCC在這個例子中產生了實際的線程本地存儲調用。 它實際上使問題變得有趣。 我還不太確定它是否適用於其他編譯器,它與直接調用線程存儲有什么不同。

我仍然支持我的一般論點,即可以以更簡單和直接的方式訪問相同的數據,無論是參數,堆棧遍歷還是線程本地存儲。

訪問當前函數調用堆棧上的數據始終是線程安全的。 這就是為什么你的代碼是線程安全的,而不是因為巧妙地使用異常。 線程本地存儲允許我們存儲每線程數據並將其引用到直接調用堆棧之外。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM