在TLS中存储映射——c++中的线程安全

Storing a Map in a TLS - Thread Safety in C++

本文关键字:安全 线程 c++ 映射 TLS 存储      更新时间:2023-10-16

基本上一个map不能被两个不同的线程同时添加/删除,所以它需要同步。

并且我知道c++中的线程本地存储概念是线程安全的,可以被多个线程访问。

    std::map<int,int> * pTemp= (std::map<int,int> *) TlsGetValue(sTlsIndex);
    if (pTemp== NULL)
    {
        pTemp= new std::map<int,int>;
        TlsSetValue(sTlsIndex, pTemp);
    }

如果我把map放在TLS中,并从不同的子线程访问它,添加/删除/修改map,那么它应该同步吗?

因为TLS已经同步了,我的map在里面。

"线程安全,可被多个线程访问"

你误解了线程本地存储——它透明地为每个线程创建不同的对象,以便该线程可以轻松/快速地找到它们并安全地使用,尽管它们不是线程安全的

如果你想让多个线程访问相同的映射,你不应该把它放在线程本地存储,应该使用std::mutex或类似的同步原语来控制访问和更新。

您似乎没有领会线程本地存储的概念。它不会同步访问同一个对象——相反,它保证每个线程都有自己的版本,这样多个线程就不会访问同一个对象,除非它们特别尝试这样做,例如,通过将指向线程本地对象的指针从一个线程传递到另一个线程。除非在非常不寻常的情况下传递这些指针,否则不会出现线程不安全问题。

它主要(我甚至敢说只有)在与静态变量结合使用时才有用。根据定义,在堆栈上创建的自动变量是线程安全的,因为总是只有一个线程使用该堆栈。

让我用一些例子来说明:

typedef std::map<int, int> my_map_t;
my_map_t getmap() {
    static my_map_t my_map = { {10, 20}, {30, 40}, {50, 60} };
    return my_map;
}
void func1() {
    getmap()[70] = 80;
}
void func2() {
    getmap()[90] = 100;
}
...
std::thread thr1(func1);
std::thread thr2(func2); 

在上面的例子中,在thr1中执行的func1()和在thr2中执行的func2()将访问同一个map实例,并将同时对其执行插入(当然可能),导致未定义的行为。此代码之后的单个映射的状态无法预测。

但是,如果按照以下方式替换getmap()的代码:

my_map_t getmap() {
    static _Thread_local my_map_t my_map = { {10, 20}, {30, 40}, {50, 60} };
    return my_map;
}

每个线程都有自己的map副本。程序中将有两个map实例,仅在一个元素上有所不同——一个将包含一对(90,100)元素(除了公共元素),另一个将包含一对(70,80)元素。这两个映射的状态是确定的,这里没有不确定性。