从另一个线程访问线程本地

Posted

技术标签:

【中文标题】从另一个线程访问线程本地【英文标题】:Access thread-local from another thread 【发布时间】:2011-03-15 09:34:45 【问题描述】:

如何从另一个线程读取/写入线程局部变量?也就是说,在线程 A 中,我想访问线程 B 的线程本地存储区中的变量。我知道另一个线程的 ID。

变量在 GCC 中被声明为 __thread。目标平台是 Linux,但独立性可能会很好(但 GCC 特定也可以)。

缺少线程启动钩子,我无法简单地在每个线程开始时跟踪该值。需要以这种方式跟踪所有线程(不仅仅是专门启动的线程)。

不能选择更高级别的包装器,例如 boost thread_local_storage 或使用 pthread 键。我需要使用真正的__thread 局部变量的性能。


第一个答案是错误的:不能将全局变量用于我想做的事情。每个线程都必须有自己的变量副本。此外,出于性能原因,这些变量必须是__thread 变量(同样有效的解决方案也可以,但我不知道)。我也不控制线程入口点,因此这些线程不可能注册任何类型的结构。


Thread Local is not private:对线程局部变量的另一个误解。这些绝不是线程的某种 private 变量。它们是全局可寻址内存,其限制是它们的生命周期与线程相关。如果给定指向这些变量的指针,则来自任何线程的任何函数都可以修改它们。上面的问题本质上是关于如何获取那个指针地址。

【问题讨论】:

当然,一般的想法是你不能。 :-) 为什么不让每个线程使用非本地数据结构报告其私有值? 大概__thread 告诉编译器它可以使用CPU 寄存器。如果是这样,由于硬件限制,这将导致无法直接访问。 @SpliFF,__thread 局部变量最终只是普通内存中的位置。你可以把它的地址交给另一个线程来访问。 @Bo,这是我对缺少启动线程挂钩的评论。我无法拦截所有线程创建并注册变量。同样,对于从拥有线程对变量的任何读取访问,我不能有函数调用的开销。 @edA-qamort-ora-y 我也想问一个非常相似的问题,但是对于线程本地存储的 Windows 实现。您是否有机会找到问题的答案/解决方案? 【参考方案1】:

如果你想要不是线程局部的线程局部变量,为什么不使用全局变量呢?

重要说明!

我并不是建议您使用单个全局变量来替换线程局部变量。我建议使用单个全局 array 或其他合适的值集合来替换一个线程局部变量。

当然,您必须提供同步,但由于您想将线程 A 中修改的值公开给线程 B,因此无法绕过。

更新:

GCC documentation on __thread 说:

当地址操作符是 应用于线程局部变量,它 在运行时评估并返回 当前线程的地址 该变量的实例。一个地址 这样获得的任何线程都可以使用。 当线程终止时,任何指针 到其中的线程局部变量 线程无效。

因此,如果您坚持这样做,我想可以在线程生成之后从它所属的线程获取线程局部变量的地址。然后,您可以将指向该内存位置的指针存储到映射(线程 id => 指针),并让其他线程以这种方式访问​​该变量。这假定您拥有生成线程的代码。

如果您真的很喜欢冒险,您可以尝试在___tls_get_addr 上挖掘信息(从this PDF 开始,由上述 GCC 文档链接)。但这种方法的编译器和平台特定性非常高,而且缺乏文档,以至于它应该会在任何人的脑海中引起警报。

【讨论】:

这不能解决我的问题。 @edA-qa mort-ora-y:我理解的问题是“我如何用锤子挖洞?”。这就是为什么我建议使用更合适的工具来完成这项工作。 @edA-qa mort-ora-y:在我看来,这个答案完美地解决了你的问题。根据定义,TLS 意味着“我不想在我的线程之间共享它”,并且编译器需要额外的工作来确保该属性。仅使用普通全局而不是尝试破解 TLS 的建议是有道理的。否则,这就像在咖啡里加盐然后倒进水槽,因为你不喜欢咖啡里加盐。 TLS 可以在线程之间共享:它和其他​​内存一样是可寻址内存。我正在寻找一种方法来发现这些变量地址,而无需源线程进行通信。 @edA-qa mort-ora-y:我知道线程本地的意思是“这个变量的多个副本”,而且原则上应该没有什么可以阻止你从“公开”一个变量。我已经更新了答案以明确说明;我仍然相信你是在逆流而上。【参考方案2】:

我正在寻找同样的东西。 正如我所见,在以各种方式搜索网络后,没有人回答您的问题,我得到了后续信息:假设在 linux (ubuntu) 上为 gcc 编译并使用 -m64,段寄存器 gs 的值为 0。隐藏部分段的(保存线性地址) 指向线程特定的局部区域。 该区域在该地址包含该地址的地址(64 位)。在较低地址存储所有线程局部变量。 该地址是native_handle()。 因此,为了访问线程本地数据,您应该通过该指针进行操作。

换句话说:(char*)&variable-(char*)myThread.native_handle()+(char*)theOtherThread.native_handle()

假设 g++,linux,pthreads 演示上述代码是:

#include <iostream>
#include <thread>
#include <sstream>

thread_local int B=0x11111111,A=0x22222222;

bool shouldContinue=false;

void code()
    while(!shouldContinue);
    std::stringstream ss;
    ss<<" A:"<<A<<" B:"<<B<<std::endl;
    std::cout<<ss.str();


//#define ot(th,variable) 
//(*( (char*)&variable-(char*)(pthread_self())+(char*)(th.native_handle()) ))

int& ot(std::thread& th,int& v)
    auto p=pthread_self();
    intptr_t d=(intptr_t)&v-(intptr_t)p;
    return *(int*)((char*)th.native_handle()+d);


int main(int argc, char **argv)
       

        std::thread th1(code),th2(code),th3(code),th4(code);

        ot(th1,A)=100;ot(th1,B)=110;
        ot(th2,A)=200;ot(th2,B)=210;
        ot(th3,A)=300;ot(th3,B)=310;
        ot(th4,A)=400;ot(th4,B)=410;

        shouldContinue=true;

        th1.join();
        th2.join();
        th3.join();
        th4.join();

    return 0;

【讨论】:

唉……太不便携了。 [如果 gcc 的 std::thread 将停止使用 pthreads 作为 native_handle 怎么办?还是结构变化?任何下一次 gcc 更新都会发生这种情况。]【参考方案3】:

很遗憾,我一直没能找到办法。

如果没有某种线程初始化钩子,似乎就无法获得该指针(缺少依赖于平台的 ASM hack)。

【讨论】:

【参考方案4】:

这是一个老问题,但既然没有给出答案,为什么不使用一个有自己的静态注册的类呢?

#include <mutex>
#include <thread>
#include <unordered_map>

struct foo;

static std::unordered_map<std::thread::id, foo*> foos;
static std::mutex foos_mutex;

struct foo

    foo()
    
        std::lock_guard<std::mutex> lk(foos_mutex);
        foos[std::this_thread::get_id()] = this;
    
;

static thread_local foo tls_foo;

当然,您需要在线程之间进行某种同步,以确保线程已经注册了指针,但是您可以从您知道线程 ID 的任何线程的映射中获取它。

【讨论】:

我给出的答案是,似乎不可能按照我的要求去做。有很多方法可以做其他事情,但问题的严格要求似乎无法满足。 我想我不明白为什么这不能满足您的要求。您不需要知道线程入口点,只需定义一个结构,该结构将在构造时注册一个指向自身的指针,然后将该结构设为您的__thread 变量。除了在线程启动时进行初始注册之外,这样做不会产生任何开销。

以上是关于从另一个线程访问线程本地的主要内容,如果未能解决你的问题,请参考以下文章

从另一个线程访问 UI 线程的视图

从另一个线程访问用户会话对象,如何实现?

您可以从另一个线程访问 UI 元素吗? (不设置)

如何从另一个活动访问在一个活动中实例化并在其自己的线程中运行的对象?

从另一个线程在表单上添加控件

在破坏调用期间从另一个线程未定义的行为调用对象上的方法?