是 C++异常足以实现线程本地存储吗?

发布于 2024-08-26 04:38:02 字数 2183 浏览 4 评论 0原文

评论一个答案 线程本地存储很好,并回忆起另一个关于我认为的异常的信息讨论

唯一特别的是 throw内的执行环境 块是异常对象是 由重新抛出引用。

将两个和两个放在一起,在其主函数的函数捕获块内执行整个线程是否会为其注入线程本地存储?

它似乎工作得很好,尽管很慢。这是新颖的还是有特色的?还有其他方法可以解决这个问题吗?我最初的前提正确吗? 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() 初始化基类,但总的来说,这并不像是一个毫无生产力、失眠的周日早晨……

< strong>编辑: 看起来 GCC 对 get_thread 中的 pthread_getspecific 进行了三次调用。 编辑:并对堆栈、环境和可执行格式进行了大量令人讨厌的内省,以找到我在第一个演练中错过的 catch 块。这看起来高度依赖于平台,因为 GCC 正在从操作系统调用一些libunwind。开销约为 4000 个周期。我想它还必须遍历类层次结构,但这可以保持在控制之下。

I was commenting on an answer that thread-local storage is nice and recalled another informative discussion about exceptions where I supposed

The only special thing about the
execution environment within the throw
block is that the exception object is
referenced by rethrow.

Putting two and two together, wouldn't executing an entire thread inside a function-catch-block of its main function imbue it with thread-local storage?

It seems to work fine, albeit slowly. Is this novel or well-characterized? Is there another way of solving the problem? Was my initial premise correct? What kind of overhead does get_thread incur on your platform? What's the potential for optimization?

#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;
}

This does require defining new exception classes derived from thread_exception_base, initializing the base with get_thread(), but altogether this doesn't feel like an unproductive insomnia-ridden Sunday morning…

EDIT: Looks like GCC makes three calls to pthread_getspecific in get_thread. EDIT: and a lot of nasty introspection into the stack, environment, and executable format to find the catch block I missed on the first walkthrough. This looks highly platform-dependent, as GCC is calling some libunwind from the OS. Overhead on the order of 4000 cycles. I suppose it also has to traverse the class hierarchy but that can be kept under control.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

久而酒知 2024-09-02 04:38:02

本着这个问题的有趣精神,我提供了这个可怕的噩梦创造:

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< /code> 从同一字符串中检索更多标记,因此在线程感知实现中它必须使用 TLS。

example *e = new example;

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

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

因此,只要 strtok 没有在程序的其他任何地方以预期的方式使用,我们就有另一个备用 TLS 插槽。

In the playful spirit of the question, I offer this horrifying nightmare creation:

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;
    }
};

Taking advantage of the fact that according to the C++ standard, strtok stashes its first argument so that subsequent calls can pass 0 to retrieve further tokens from the same string, so therefore in a thread-aware implementation it must be using TLS.

example *e = new example;

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

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

So as long as strtok is not used in the intended way anywhere else in the program, we have another spare TLS slot.

手长情犹 2024-09-02 04:38:02

我认为你在这里有所发现。正如您所提到的,这甚至可能是一种将数据放入不接受用户“状态”变量的回调中的可移植方法,即使不显式使用线程也是如此。

听起来您已经回答了主题中的问题:是的。

I think you're onto something here. This might even be a portable way to get data into callbacks that don't accept a user "state" variable, as you've mentioned, even apart from any explicit use of threads.

So it sounds like you've answered the question in your subject: YES.

甜警司 2024-09-02 04:38:02
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 在此示例中生成实际的线程本地存储调用。这实际上让这个问题变得有趣。我仍然不太确定其他编译器是否也是如此,以及它与直接调用线程存储有何不同。

我仍然坚持我的一般观点,即可以以更简单和直接的方式访问相同的数据,无论是参数、堆栈遍历还是线程本地存储。

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); }

I might be missing something here, but it's not a thread local storage, just unnecessarily complicated argument passing. Argument is different for each thread only because it is passed to pthread_create, not because of any exception juggling.


It turned out that I indeed was missing that GCC is producing actual thread local storage calls in this example. It actually makes the issue interesting. I'm still not quite sure whether it is a case for other compilers, and how is it different from calling thread storage directly.

I still stand by my general argument that the same data can be accessed in a more simple and straight-forward way, be it arguments, stack walking or thread local storage.

笙痞 2024-09-02 04:38:02

访问当前函数调用堆栈上的数据始终是线程安全的。这就是为什么你的代码是线程安全的,而不是因为巧妙地使用了异常。线程本地存储允许我们存储每个线程的数据并在立即调用堆栈之外引用它。

Accessing data on the current function call stack is always thread safe. That's why your code is thread safe, not because of the clever use of exceptions. Thread local storage allows us to store per-thread data and reference it outside of the immediate call stack.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文