std::map::end 线程安全,并保证它对于同一容器始终相同

Is std::map::end thread-safe and is guaranteed that it always the same for the same container?

本文关键字:于同一 线程 end map 安全 std      更新时间:2023-10-16

我使用std::map并得到一个我可以使用的元素:http://www.cplusplus.com/reference/map/map/

  • iterator find (const key_type& k);
  • mapped_type& at (const key_type& k);
  • mapped_type& operator[] (const key_type& k);

另外:在这种情况下,lower_bound()equal_range() - 与find()相同。

我不能使用

  • at() - 因为它抛出异常,并且我测量了 10 倍的性能下降
  • operator[] - 因为它插入一个元素(如果不存在),所以这种行为是不可接受的

find() - 是我想要的。但是我在多线程程序中使用std::map并通过锁定std::mutex来保护它。

还可以插入和删除以从其他线程std::map

我应该保护std::map::end还是保证一个分配的容器始终相同?

我可以使用这样不受std::mutex保护的static auto const map_it_end = map1.end();的东西吗?

http://ideone.com/tATn0H

#include <iostream>
#include <string>
#include <mutex>
#include <thread>
#include <map>
std::map<std::string, std::string> map1 ( {{"apple","red"},{"lemon","yellow"}} );
static auto const map_it_end = map1.end();
std::mutex mtx1;
void func() {
    std::lock_guard<std::mutex> lock1(mtx1);
    auto it1 = map1.find("apple");
    if(it1 != map_it_end)   // instead of: if(it1 != map1.end())
        std::cout << it1->second << ", ";
}
int main ()
{
    std::thread t1(func);
    std::thread t2(func);
    t1.join();
    t2.join();
    return 0;
}

http://www.cplusplus.com/reference/map/map/end/

数据竞赛 容器被访问(既不是常量也不是 非常量版本修改容器)。不包含任何元素 由调用访问,但返回的迭代器可用于访问 或修改元素。同时访问或修改不同的 元素是安全的。

我应该保护std::map::end还是保证一个分配的容器始终相同?

从技术上讲,如果对成员函数的任何调用可能与任何非常量成员函数同时发生,则必须受互斥锁保护。因此,如果任何线程可能插入或擦除元素,那么在不锁定互斥锁的情况下调用end()是不安全的。

我可以使用这样不受std::mutex保护的static auto const map_it_end = map1.end();的东西吗?

在某些情况下,您可以缓存过去结束迭代器

,因为std::map的过去结束迭代器不会因插入和擦除而失效,只是可能通过交换或移动映射而失效。

但是你为什么要这样做呢?缓慢的操作find()end(),所以如果你在仍然持有互斥锁的同时调用end(),那么它肯定有效。

如果其他线程可能正在擦除元素,那么在取消引用 find() 返回的迭代器时,您需要持有互斥锁,以确保它不会被另一个线程擦除它引用的元素而失效。因此,当您已经锁定互斥锁时,调用end()不会成为问题。

我在23.2 Container requirements中找不到任何指定end()始终返回相同值的内容,也没有发现它是线程安全的。 end()定义如下。

begin() 返回一个迭代器,引用 容器。end() 返回一个迭代器,它是过去的结束值 对于容器。如果容器为空,则 begin() == end();

此规范似乎涵盖了所有容器的end()。我在23.4.4 Class template map中找不到任何可以取代此一般容器要求的内容。实际上,"过去结束值"的措辞是这样的,因此可以合理地解释为end()的值可能会根据容器中最后一个元素的位置而变化。

这将是std::vector的典型情况.一个典型的std::vector end()值会根据向量中元素的数量而变化,原因很明显。没有任何规定必须这样做,但通常是这种情况。回到std::map,人们可能会期望给定地图的end()总是相同的值,但也没有说明它必须这样做。

我想说的是,对std::map的所有访问都必须受到互斥锁的保护。一旦互斥锁被释放,关于映射的任何内容都不再有效。不能假设end()在互斥锁发布后仍将是有效的迭代器。