std::map的只读操作是否线程安全

22
我有一个std::map,用于将值(字段ID)映射到可读的字符串。在启动任何其他线程之前,该映射只初始化一次,并且此后不再修改。目前,我为每个线程提供其自己的该映射的副本,但这显然浪费了内存并减慢了程序启动速度。因此,我考虑给每个线程一个指向该映射的指针,但这会带来线程安全问题。
如果我只是使用以下代码从该映射中读取:
std::string name;
//here N is the field id for which I want the human readable name
unsigned field_id = N; 
std::map<unsigned,std::string>::const_iterator map_it;

// fields_p is a const std::map<unsigned, std::string>* to the map concerned.
// multiple threads will share this.
map_it = fields_p->find(field_id);
if (map_it != fields_p->end())
{
    name = map_it->second;
}
else
{
    name = "";
}
这个方法可行吗?或者在多个线程中读取std::map会出现问题吗?
备注:目前我正在使用Visual Studio 2008,但我希望这适用于大多数主要的STL实现。
更新:编辑了代码示例以解决const正确性问题。
4个回答

17
只要您使用的映射表保持不变,这段代码就可以在多个线程中正常工作。事实上,您使用的映射表是不可变的,所以任何查找操作都会在一个不改变的映射表中进行。以下是相关链接:http://www.sgi.com/tech/stl/thread_safety.html
引用: "SGI STL的实现仅在以下情况下具有线程安全性:对不同容器的同时访问是安全的,并且对共享容器的同时读取访问是安全的。如果多个线程访问单个容器,并且至少有一个线程可能进行写入,则用户需要确保在线程进行容器访问期间进行相互排斥。"
您属于“对共享容器的同时读取访问”类别。请注意:这仅适用于SGI的实现。如果您使用其他实现,请先检查其是否具有相同的特性。我了解到,STLPort具有内置的线程安全性。至于Apache实现,我不确定。

1
注意:答案仅限于SGI STL实现。OP没有提到使用哪个。 - foraidt
我使用的是Visual Studio 2008附带的实现,但我正在寻找涵盖std :: map的一般答案,或者至少跨大多数实现(如果可能的话)。 - jilles de wit
请查看STL/CRL文档(适用于VS2008):http://msdn.microsoft.com/en-us/library/bb385954.aspx。在我看来,这对于VS2008实现来说是不可能不正确的:它使用平衡二叉树以及支持映射。但我相信他们在文档中一定有关于线程安全的注释。 - laura
我并不信服。仅仅因为没有人修改这个映射表并不一定意味着它是线程安全的。我们不知道对象内部的细节。可能会发生一些我们不知道的底层操作,比如缓存。(我曾经写过一个哈希表,它缓存了最近返回的键值对) - Edward Falk

9

应该没问题。 如果您想记录/强制只读行为,可以使用const引用。

请注意,即使您仅使用const方法(一个非常恶劣的实现可能会将树声明为可变的),也不能保证正确性(原则上,映射可以选择在调用find时重新平衡)。但是,在实践中,这似乎相当不可能。


我稍微更新了我的代码,使用了一个对映射表的常量引用和一个const_iterator。 - jilles de wit
@Useless:不一定。伸展树在查找期间更新树,这使得最近访问的元素更快地查找。 - Jørgen Fogh
1
好的观点,尽管理想情况下这样的映射不会提供const访问器,然后使其树可变。另外:重振旧话题的好方法! :-) - Useless
在我看来,如果一个类被记录为非线程安全(更具体地说:没有被记录为线程安全),那么它可以采用非线程安全的方式实现其内部。 - Edward Falk
总的来说,您是完全正确的,这就是为什么我说不能保证正确性的原因。然而,我查看过的所有标准库实现中,都没有通过const访问器/迭代器来改变mutable实现细节。在红黑树中使用它们没有任何意义。我当然不知道是否有任何使用伸展树实现的std::map - Useless
显示剩余2条评论

3

0

针对 MS STL 实现

C++标准库中的线程安全性

以下线程安全规则适用于 C++ 标准库中的所有类——包括下面描述的 shared_ptr。有时会提供更强的保证——例如,标准 iostream 对象,如下所述,以及专门用于多线程的类型,如 .

一个对象在多个线程中进行读取是线程安全的。例如,给定一个对象 A,从线程 1 和线程 2 同时读取 A 是安全的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接