【发布时间】:2014-01-17 02:12:54
【问题描述】:
我的问题是,无论线程如何访问包含的对象,向量、集合、队列、映射、多映射等容器是否提供标准的线程安全保证(即并发线程可以调用 const 方法等) .
简单地说:如果你锁定一个地图以供读取,只要你不插入,你能安全地(就地图而言)修改包含的对象(在这种情况下是值)或删除项目或以其他方式在地图上调用非常量方法?
【问题讨论】:
标签: c++ multithreading c++-standard-library
我的问题是,无论线程如何访问包含的对象,向量、集合、队列、映射、多映射等容器是否提供标准的线程安全保证(即并发线程可以调用 const 方法等) .
简单地说:如果你锁定一个地图以供读取,只要你不插入,你能安全地(就地图而言)修改包含的对象(在这种情况下是值)或删除项目或以其他方式在地图上调用非常量方法?
【问题讨论】:
标签: c++ multithreading c++-standard-library
我的问题是,无论线程如何访问包含的对象,向量、集合、队列、映射、多映射等容器是否提供标准的线程安全保证(即并发线程可以访问 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_ += 3 或n_ -= 3(包含的对象)的安全性如何,m(容器)仍然提供相同的保证,对吗?
std::map<int, pthread_mutex_t> m2; void my_thread(int x) { rw_lock.r_lock(); pthread_mutex_lock(m2.find(x).second); rw_lock.r_unlock(); }
rw_lock,并且找到了x,而忽略了它应该是find(x)->...; -))。有了map,没有rw_lock 就不会安全的事情包括例如.clear() 和 .erase 对该特定节点的操作,以及为仍在插入的 x 调用 my_thread(x),因为树节点可能未完全链接和/或互斥锁本身可能未正确初始化却让pthread_mutex_lock() 打电话很危险....
我认为C++11 STL containers and thread safety 可能已经回答了这个问题。 只要你能保证你的线程永远不会访问彼此相同的元素,那么它们就可以对它们做任何他们喜欢的事情。容器本身不保证线程安全。
【讨论】:
不保证 STL 容器是线程安全的。
并发读取是可以的(您不要更改容器中的元素或容器的任何属性)。
还要注意迭代器验证,某些函数会使部分或全部迭代器失效。 (你可以从 cplusplus 或 cppreference 中找到信息)
只要在访问容器之前锁定容器(使用互斥锁来保护容器),它就是安全的。 如果你需要更好的性能,你可能需要实现一个 rw_lock。
【讨论】: