C++ 在映射中使用std::thread::id使线程安全

C++ 在映射中使用std::thread::id使线程安全,c++,multithreading,C++,Multithreading,想法是为每个线程创建实例,因此我为每个新的thread::id创建了新实例,如下所示: struct doSomething{ void test(int toto) {} }; void test(int toto) { static std::map<std::thread::id, doSomething *> maps; std::map<std::thread::id, doSomething *>::iterator it = ma

想法是为每个线程创建实例,因此我为每个新的
thread::id
创建了新实例,如下所示:

struct doSomething{
    void test(int toto) {}
};

void test(int toto)
{
    static std::map<std::thread::id, doSomething *> maps;

    std::map<std::thread::id, doSomething *>::iterator it = maps.find(std::this_thread::get_id());
    if (it == maps.end())
    {
        // mutex.lock() ?
        maps[std::this_thread::get_id()] = new doSomething();
        it = maps.find(std::this_thread::get_id());
        // mutex.unlock() ?
    }
    it->second->test(toto);
}
struct doSomething{
无效测试(int toto){}
};
无效测试(int toto)
{
静态std::地图;
std::map::iterator it=maps.find(std::this_thread::get_id());
if(it==maps.end())
{
//mutex.lock()?
映射[std::this_thread::get_id()]=new doSomething();
it=maps.find(std::this_thread::get_id());
//mutex.unlock()?
}
it->second->test(toto);
}
这是个好主意吗?不,不是个好主意

std::map
的方法本身不是线程安全的

为了使它真正成为一个“好主意”,还必须使用互斥锁或等效工具,使对
std::map
的所有访问都是线程安全的

这不仅包括您注释掉的部分,还包括您正在使用的所有其他方法,如
find
()

所有与您的
std::map
相关的内容都必须受到互斥保护。

不,这不是个好主意

std::map
的方法本身不是线程安全的

为了使它真正成为一个“好主意”,还必须使用互斥锁或等效工具,使对
std::map
的所有访问都是线程安全的

这不仅包括您注释掉的部分,还包括您正在使用的所有其他方法,如
find
()


所有与您的
std::map
相关的内容都必须受到互斥锁保护。

在访问映射后拥有互斥锁是不够的。如果没有互斥体,您不能在地图附近的任何地方移动,因为在您读取地图时,另一个线程可能会使用互斥体来修改地图

{
    std::unique_lock<std::mutex> lock(my_mutex);
    std::map<std::thread::id, doSomething *>::iterator it = maps.find(std::this_thread::get_id());
    if (it != maps.end())
        return *it;
    auto ptr = std::make_unique<doSomething>();
    maps[std::this_thread::get_id()] = ptr.get();
    return ptr.release();
}
现场演示:


这有点难以识别,但线程'9200已分配..4a80。。而线程'1904已分配..4b00..

在访问映射后拥有互斥锁是不够的。如果没有互斥体,您不能在地图附近的任何地方移动,因为在您读取地图时,另一个线程可能会使用互斥体来修改地图

{
    std::unique_lock<std::mutex> lock(my_mutex);
    std::map<std::thread::id, doSomething *>::iterator it = maps.find(std::this_thread::get_id());
    if (it != maps.end())
        return *it;
    auto ptr = std::make_unique<doSomething>();
    maps[std::this_thread::get_id()] = ptr.get();
    return ptr.release();
}
现场演示:


这有点难以识别,但线程'9200已分配..4a80。。鉴于线程'1904分配了..4b00..

如果C++11由于线程的本地支持而可用,这是一个很好的答案。如果C++11不可用,一个很好的方法是线程创建者通过线程参数(指向包含上下文的对象的指针)向其提供所有必要的线程上下文。大多数编译器使用线程本地存储已有十多年了。这是真的,但是如果没有C++11,TLS就不那么简单了,尤其是在编写跨平台代码时。这就是为什么我建议传递context对象,因为与TLS相比,这是一种更简单的方法,而且每个线程库都支持将void*传递给新线程的概念。如果C++11因为thread_local支持而可用,这是一个很好的答案。如果C++11不可用,一个很好的方法是线程创建者通过线程参数(指向包含上下文的对象的指针)向其提供所有必要的线程上下文。大多数编译器使用线程本地存储已有十多年了。这是真的,但是如果没有C++11,TLS就不那么简单了,尤其是在编写跨平台代码时。这就是为什么我建议传递context对象,因为与TLS相比,它是一种更简单的方法,而且每个线程库都支持将void*传递给新线程的概念。
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
140551597459200 allocated 0x7fd4a80008e0
140551597459200 running doSomething 0x7fd4a80008e0
140551605851904 allocated 0x7fd4b00008e0
140551605851904 running doSomething 0x7fd4b00008e0
140551605851904 re-use
140551605851904 running doSomething 0x7fd4b00008e0
140551597459200 re-use
140551605851904 re-use
140551597459200 running doSomething 0x7fd4a80008e0
140551605851904 running doSomething 0x7fd4b00008e0
140551597459200 re-use
140551597459200 running doSomething 0x7fd4a80008e0