7

我在运行一些传给我的旧代码时遇到了问题。它在 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[] 实现中抛出异常,在第一次调用 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。

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

4

5 回答 5

5

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

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

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 问题。

于 2008-10-23T23:19:37.217 回答
3

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

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

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

于 2008-10-23T23:09:35.753 回答
2

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

if (mappedChars.empty())

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

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

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

于 2008-10-23T23:09:03.727 回答
1

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

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

于 2008-10-23T23:26:48.793 回答
0

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

于 2008-10-24T04:40:11.517 回答