【问题标题】:C++ Standard Library container thread-safety with respect to contained objects相对于包含对象的 C++ 标准库容器线程安全
【发布时间】:2014-01-17 02:12:54
【问题描述】:

我的问题是,无论线程如何访问包含的对象,向量、集合、队列、映射、多映射等容器是否提供标准的线程安全保证(即并发线程可以调用 const 方法等) .

简单地说:如果你锁定一个地图以供读取,只要你不插入,你能安全地(就地图而言)修改包含的对象(在这种情况下是值)或删除项目或以其他方式在地图上调用非常量方法?

【问题讨论】:

    标签: c++ multithreading c++-standard-library


    【解决方案1】:

    我的问题是,无论线程如何访问包含的对象,向量、集合、队列、映射、多映射等容器是否提供标准的线程安全保证(即并发线程可以访问 const 成员等) .

    不,不是“无论如何”。给定“并发线程 [访问] const 成员” [容器],它们可以获得对存储元素的 const 访问权限,但容器无法对不合法的对象执行任何操作,如果对象是例如局部变量 - 即您不能以线程不安全的方式调用影响可变或静态变量的方法。

    简单地说:如果你锁定一个地图以供读取,只要你不插入,你能安全地(就地图而言)修改包含的对象(在这种情况下是值)或删除项目或以其他方式在地图上调用非常量方法?

    如果“锁定地图以供读取”是指您的程序具有单独的读/写锁并在访问地图之前获得“读者”锁定状态,那么不 - 如果其他读者无法修改包含的对象可能正在访问它们。为了确保安全,您需要一个围绕映射使用的互斥锁,就像线程在局部变量上操作一样。


    示例

    下面,一个空行分隔示例,第一列和第二列列出了来自两个线程的命令,它们可以按顺序执行或同时执行。请注意,仅仅因为某些东西是“安全”的执行并不意味着更新将在其他线程中可见,直到完成一些显式的内存屏障或缓存刷新操作 - 这取决于您的硬件:“正确的”互斥锁/rwlocks等。倾向于照顾它。

    class X { int n_; std::string s_; } x;
    
    std::vector<X> v = ...;
    std::map<int,X> m = ...;
    
    thread 1                thread 2                  safe?
    
    v.push_back(...);       ++v[0].n_;                Precondition: 1 <= size() < capacity()
                                                      (i.e. safe iff v[0] can't be moved)
    
    v.some-const-member();  v.another-const-member(); YES - e.g. [n], find(), begin()
    
    v[0].s_ = "hi";         std::cout << v[0].s_;     NO - as for any string var
    
    v[0].s_.size();         std::cout << v[0].s_;     YES - as for any string var
    
    rw_lock.r_lock() LOCKED
    iterator i = m.find(7); rw_lock.w_lock() BLOCK
    rw_lock.r_unlock()      ....
                            LOCKED
    std::cout << i->second; m.insert(...);            YES - insert can't invalidate i
    i->second.n_ += 3;      m.find(7).n_ -= 3;        NO - as per any int var
    

    【讨论】:

    • 感谢您的详细示例。我的问题实际上与您的最后一个问题很接近。无论n_ += 3n_ -= 3(包含的对象)的安全性如何,m(容器)仍然提供相同的保证,对吗?
    • 好的,让我们更明确一点。下面安全吗? std::map&lt;int, pthread_mutex_t&gt; m2; void my_thread(int x) { rw_lock.r_lock(); pthread_mutex_lock(m2.find(x).second); rw_lock.r_unlock(); }
    • @nccc: 是的,这很安全(假设显然其他线程也在他们的访问中使用rw_lock,并且找到了x,而忽略了它应该是find(x)-&gt;...; -))。有了map,没有rw_lock 就不会安全的事情包括例如.clear().erase 对该特定节点的操作,以及为仍在插入的 x 调用 my_thread(x),因为树节点可能未完全链接和/或互斥锁本身可能未正确初始化却让pthread_mutex_lock() 打电话很危险....
    【解决方案2】:

    我认为C++11 STL containers and thread safety 可能已经回答了这个问题。 只要你能保证你的线程永远不会访问彼此相同的元素,那么它们就可以对它们做任何他们喜欢的事情。容器本身不保证线程安全。

    【讨论】:

      【解决方案3】:

      不保证 STL 容器是线程安全的。

      并发读取是可以的(您不要更改容器中的元素或容器的任何属性)。

      还要注意迭代器验证,某些函数会使部分或全部迭代器失效。 (你可以从 cplusplus 或 cppreference 中找到信息)

      只要在访问容器之前锁定容器(使用互斥锁来保护容器),它就是安全的。 如果你需要更好的性能,你可能需要实现一个 rw_lock。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-25
        • 2020-12-12
        • 1970-01-01
        相关资源
        最近更新 更多