【问题标题】:Violation reading location in std::map operator[]std::map operator[] 中的违规读取位置
【发布时间】:2010-09-18 21:57:01
【问题描述】:

我在运行一些传给我的旧代码时遇到了问题。它在 99% 的时间内都有效,但有时我注意到它会抛出“违反读取位置”异常。我有可变数量的线程可能在整个进程的生命周期中执行此代码。低发生频率可能​​表明存在竞争条件,但我不知道为什么在这种情况下会导致异常。这是有问题的代码:

MyClass::Dostuff()
{
    static map<char, int> mappedChars;
    if (mappedChars.empty())
    {
       for (char c = '0'; c <= '9'; ++c)
       {
          mappedChars[c] = c - '0';
       }
    }
    // More code here, but mappedChars in not changed.
}

在 map 的 operator[] 实现中,第一次调用 operator[] 时抛出异常(使用 STL 的 VS2005 实现。)


mapped_type& operator[](const key_type& _Keyval)
{
    iterator _Where = this->lower_bound(_Keyval); //exception thrown on the first line
    // More code here
}

我已经尝试冻结 operator[] 中的线程并尝试让它们同时运行,但我无法使用该方法重现异常。

你能想到什么原因会抛出,而且只是在某些时候?

(是的,我知道 STL 不是线程安全的,我需要在这里进行更改。我很好奇为什么会看到我上面描述的行为。)

根据要求,此处提供有关异常的更多详细信息:
app15-51-02-0944_2008-10-23.mdmp 中 0x00639a1c (app.exe) 处未处理的异常:0xC0000005:访问冲突读取位置 0x00000004。

感谢大家提出多线程问题的解决方案,但这不是这个问题要解决的问题。是的,我了解所提供的代码没有得到正确的保护,并且在它试图完成的事情上过于矫枉过正。我已经实现了它的修复。我只是想更好地理解为什么会引发这个异常。

【问题讨论】:

  • 了解违规地址可能很有用。有可能“this”为空,与地图本身无关。

标签: c++ multithreading exception stl


【解决方案1】:

给定地址“4”,可能“this”指针为空或迭代器错误。您应该能够在调试器中看到这一点。如果这是 null,那么问题不在于该函数,而在于调用该函数的人。如果迭代器不好,那么它就是你提到的竞争条件。大多数迭代器无法容忍列表被更新。

好的,等等 - 这里没有 FM。静态在第一次使用时被初始化。执行此操作的代码不是多线程安全的。一个线程正在进行初始化,而第二个线程认为它已经完成但仍在进行中。结果是使用了未初始化的变量。您可以在下面的程序集中看到这一点:

static x y;
004113ED  mov         eax,dword ptr [$S1 (418164h)] 
004113F2  and         eax,1 
004113F5  jne         wmain+6Ch (41141Ch) 
004113F7  mov         eax,dword ptr [$S1 (418164h)] 
004113FC  or          eax,1 
004113FF  mov         dword ptr [$S1 (418164h)],eax 
00411404  mov         dword ptr [ebp-4],0 
0041140B  mov         ecx,offset y (418160h) 
00411410  call        x::x (4111A4h) 
00411415  mov         dword ptr [ebp-4],0FFFFFFFFh

$S1 在初始化时设置为 1。如果设置,(004113F5) 它将跳过初始化代码 - 冻结 fnc 中的线程将无济于事,因为此检查是在进入函数时完成的。这不是 null,但其中一个成员是。

通过将地图从方法中移出并作为静态的类移入来进行修复。然后它将在启动时初始化。否则,您必须在调用 doStuff() 周围放置一个 CR。您可以通过在地图本身的使用周围放置一个 CR(例如,DoStuff 使用 operator[] 的地方)来防止剩余的 MT 问题。

【讨论】:

  • 你是说地图的this指针?地图是静态的,在 for 循环之外永远不会改变。
  • 是的,看起来就是这样。但我同意,如果地图的唯一访问权限是 mappedChars[c],这是没有意义的。迭代器可能是由竞争条件导致的 null - 返回的迭代器,另一个线程更新的映射使其无效。
  • @me.yahoo.com:一个问题是需要构建静态地图。线程 A 出现并开始构建它,但在此之前,线程 B 出现,认为它已构建并开始使用它。巴姆。另一种情况是不同的线程认为他们需要构造。巴姆。
  • 同意,另一种情况是它被构建了两次。
【解决方案2】:

mappedChars 是静态的,因此它由执行 DoStuff() 的所有线程共享。仅此一项就可能是您的问题。

如果您必须使用静态映射,那么您可能需要使用互斥锁或临界区来保护它。

就个人而言,我认为为此目的使用地图是矫枉过正的。我会编写一个辅助函数,它接受一个字符并从中减去“0”。函数不会有任何线程安全问题。

【讨论】:

    【解决方案3】:

    如果多个线程正在调用函数DoStuff,这将意味着初始化代码

    if (mappedChars.empty())
    

    可以进入竞争条件。这意味着线程 1 进入函数,发现映射为空并开始填充它。然后线程 2 进入函数并发现映射非空(但未完全初始化),因此愉快地开始读取它。因为两个线程现在都在争用,但一个正在修改映射结构(即插入节点),将导致未定义的行为(崩溃)。

    如果您在检查映射之前使用同步原语empty(),并在保证映射完全初始化后释放,一切都会好起来的。

    我通过Google 看了一眼,确实静态初始化不是线程安全的。因此声明static mappedChars 立即成为一个问题。正如其他人所提到的,最好在初始化的生命周期内保证只有 1 个线程处于活动状态时完成初始化。

    【讨论】:

    • 请注意,写入线程还没有开始填充它。它在 operator[] 的第一行抛出。据我所知,lower_bound 不会改变地图的内容。
    • 其他线程在做什么,是否还有其他线程也在访问这个共享变量?我不记得编译器为静态变量插入的代码本身是否是线程安全的。
    【解决方案4】:

    当您开始使用多线程时,通常有太多事情无法确定问题的确切位置,因为它总是会发生变化。有很多地方在多线程情况下使用静态地图可能会出错。

    请参阅this thread 了解一些保护静态变量的方法。您最好的选择可能是在启动多个线程以对其进行初始化之前调用该函数一次。要么这样,要么将静态地图移出,并创建一个单独的初始化方法。

    【讨论】:

      【解决方案5】:

      您是否曾经使用不在0..9 范围内的参数调用operator[]?如果是这样,那么您无意中修改了映射,这可能会导致其他线程中发生错误。如果您调用 operator[] 并使用映射中尚未存在的参数,它会将该键插入映射中,其值等于值类型的默认值(在 int 的情况下为 0)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-02-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-07
        • 1970-01-01
        • 2022-08-24
        相关资源
        最近更新 更多