繁体   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