std :: map operator []中的违规读取位置

时间:2008-10-23 23:00:08

标签: c++ multithreading exception stl

我在运行一些传给我的旧代码时遇到了问题。它可以在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.
}

在第一次调用operator []时,在map的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:0xC0000005:0x00639a1c(app.exe)处于未处理的异常:访问冲突读取位置0x00000004。

感谢大家提出多线程问题的解决方案,但这不是这个问题要解决的问题。是的,我理解所呈现的代码没有得到正确的保护,并且在它试图完成的内容方面有些过分。我已经有了实现它的修复程序。我只是想更好地理解为什么要抛出这个异常。

5 个答案:

答案 0 :(得分:5)

给定地址“4”,可能“this”指针为null或迭代器不好。您应该能够在调试器中看到这一点。如果这是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来保护其免受MT问题的影响(例如,DoStuff使用operator [])。

答案 1 :(得分:3)

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

如果必须使用静态地图,则可能需要使用互斥锁或临界区保护它。

就个人而言,我认为为此目的使用地图是过度的。我会写一个辅助函数,它接受一个char并从中减去'0'。一个函数不会出现任何线程安全问题。

答案 2 :(得分:2)

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

if (mappedChars.empty())

可以进入比赛状态。这意味着线程1进入函数,发现地图为空并开始填充它。线程2然后进入函数并发现地图非空(但未完全初始化),所以快乐地开始阅读它。因为两个线程现在都处于争用状态,但是正在修改映射结构(即插入节点),将导致未定义的行为(崩溃)。

如果在检查地图empty()之前使用同步原语,并在保证地图完全初始化后发布,那么一切都会很好。

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

答案 3 :(得分:1)

当你进入多线程时,通常会有太多的东西来确定事情变坏的确切位置,因为它总会发生变化。有很多地方在多线程情况下使用静态地图可能会变坏。

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

答案 4 :(得分:0)

您是否曾使用不在operator[]范围内的参数调用0..9?如果是这样,那么你无意中修改了地图,这可能会导致其他线程中出现不良情况。如果使用参数中尚未包含的参数调用operator[],它会将该键插入到映射中,其值等于值类型的默认值(在int的情况下为0)。 / p>