提问人:Marcin 提问时间:10/24/2008 最后编辑:Marcin 更新时间:10/24/2008 访问量:5762
违反 std::map 运算符中的读取位置
Violation reading location in std::map operator[]
问:
我在运行一些传给我的旧代码时遇到了问题。它在 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的访问冲突。
感谢大家为多线程问题提供解决方案,但这不是这个问题要解决的问题。是的,我知道所呈现的代码没有得到正确的保护,并且在它试图完成的事情上是矫枉过正的。我已经实施了它的修复程序。我只是想更好地理解为什么一开始就抛出这个异常。
答:
如果多个线程正在调用该函数,这将意味着初始化代码DoStuff
if (mappedChars.empty())
可以进入竞争条件。这意味着线程 1 进入函数,发现地图为空并开始填充它。然后线程 2 进入函数并发现映射不为空(但未完全初始化),因此愉快地开始读取它。因为两个线程现在都处于争用状态,但其中一个线程正在修改映射结构(即插入节点),所以将导致未定义的行为(崩溃)。
如果在检查映射之前使用同步原语,并在保证映射已完全初始化后释放,则一切都会好起来的。empty()
我通过谷歌看了一下,静态初始化确实不是线程安全的。因此,声明立即成为一个问题。正如其他人所提到的,最好在保证只有 1 个线程在初始化的生命周期内处于活动状态时完成初始化。static mappedChars
评论
mappedChars 是静态的,因此它由执行 DoStuff() 的所有线程共享。仅此一项就可能是您的问题。
如果必须使用静态映射,则可能需要使用互斥锁或关键部分来保护它。
就我个人而言,我认为为此目的使用地图是矫枉过正的。我会编写一个辅助函数,该函数接受一个字符并从中减去“0”。函数不会有任何线程安全问题。
给定地址为“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
$S 1 在初始化时设置为 1。如果设置了 (004113F5),它会跳过初始化代码 - 冻结 fnc 中的线程将无济于事,因为此检查是在进入函数时完成的。这不是 null,但其中一个成员是 null。
通过将映射移出方法并作为静态移动到类中来修复。然后它将在启动时初始化。否则,您必须在调用 do DoStuff() 周围放置一个 CR。您可以通过在地图本身的使用周围放置一个 CR 来防止剩余的 MT 问题(例如,DoStuff 使用 operator[])。
评论
当你进入多线程时,通常会有太多的事情要做,以确定事情变得糟糕的确切位置,因为它总是在变化。在很多情况下,在多线程情况下使用静态映射可能会变坏。
有关保护静态变量的一些方法,请参阅此线程。最好的办法可能是在启动多个线程来初始化它之前调用该函数一次。或者,或者将静态映射移出,并创建一个单独的初始化方法。
你有没有打电话提出一个不在范围内的论点?如果是这样,那么您无意中修改了地图,这可能会导致其他线程发生错误。如果使用映射中尚不存在的参数进行调用,则会将该键插入到映射中,其值等于值类型的默认值(如果为 0)。operator[]
0..9
operator[]
int
上一个:常见 WCF 异常:连接意外关闭
评论