std::map 运算符[] 中的读取位置违规

发布于 2024-07-07 10:14:40 字数 1104 浏览 3 评论 0原文

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

第一次调用操作符[]时,在地图的操作符[]实现中抛出异常(使用STL的VS2005实现。)


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

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

你能想到为什么会发生这种情况,而且只是在某些时候发生吗?

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

根据要求,这里有一些有关异常的进一步详细信息:
app15-51-02-0944_2008-10-23.mdmp 中 0x00639a1c (app.exe) 处出现未处理的异常:0xC0000005:读取位置 0x00000004 时出现访问冲突。

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

I encountered a problem when running some old code that was handed down to me. It works 99% of the time, but once in a while, I notice it throwing a "Violation reading location" exception. I have a variable number of threads potentially executing this code throughout the lifetime of the process. The low occurrence frequency may be indicative of a race condition, but I don't know why one would cause an exception in this case. Here is the code in question:

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.
}

The exception is thrown in the map's operator[] implementation, on the very first call to the operator[] (Using the VS2005 implementation of STL.)


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

I already tried freezing threads in operator[] and trying to get them to all run through it at the same time, but I was unable to reproduce the exception using that methodology.

Can you think of any reason why that would throw, and only some of the time?

(Yes, I know STL is not thread-safe and I'll need to make changes here. I am mostly curious as to WHY I'm seeing the behavior I described above.)

As requested, here some further details about the exception:
Unhandled exception at 0x00639a1c (app.exe) in app15-51-02-0944_2008-10-23.mdmp: 0xC0000005: Access violation reading location 0x00000004.

Thanks to everyone suggesting solutions to multithreading issues, but this isn't what this question is meant to address. Yes, I understand the presented code is not protected correctly and is overkill in what it's trying to accomplish. I already have the fix for it implemented. I'm just trying to get a better understanding of why this exception was thrown to begin with.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

夏末的微笑 2024-07-14 10:14:41

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

请参阅此线程了解保护静态变量的一些方法。 最好的选择可能是在启动多个线程对其进行初始化之前调用该函数一次。 或者,或者将静态映射移出,并创建一个单独的初始化方法。

When you get into multi-threading, there's usually too much going on to pinpoint the exact spot where things are going bad, since it will always be changing. There are a ton of spots where using a static map in a multithreaded situation could go bad.

See this thread for some of the ways to guard a static variable. Your best bet would probably be to call the function once before starting up multiple threads to initialize it. Either that, or move the static map out, and create a separate initialization method.

难理解 2024-07-14 10:14:41

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

Are you ever calling operator[] with an argument that's not in the range 0..9? If so, then you are inadvertently modifying the map, which is likely causing badness to happen in other threads. If you call operator[] with an argument not already in the map, it inserts that key into the map with a value equal to the default value of the value type (0 in the case of int).

趴在窗边数星星i 2024-07-14 10:14:40

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

好的,等等 - 这里没有 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) 它会跳过 init 代码 - 冻结 fnc 中的线程不会有帮助,因为此检查是在函数入口处完成的。 这不是空的,但其中一个成员是空的。

通过将映射移出方法并以静态方式移入类来修复。 然后它将在启动时初始化。 否则,您必须在调用 doDoStuff() 周围放置 CR。 您可以通过在地图本身的使用周围放置 CR(例如,DoStuff 使用运算符 [] 的地方)来防止剩余的 MT 问题。

Given an address of "4", Likely the "this" pointer is null or the iterator is bad. You should be able to see this in the debugger. If this is null, then the problem isn't in that function but who ever is calling that function. If the iterator is bad, then it's the race condition you alluded to. Most iterators can't tolerate the list being updated.

Okay wait - No FM here. Static's are initialized on first use. The code that does this is not multi-thread safe. one thread is doing the initialization while the 2nd thinks it's already been done but it's still in progress. The result is that is uses an uninitialized variable. You can see this in the assembly below:

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

The $S1 is set to 1 when it init's. If set, (004113F5) it jumps over the init code - freezing the threads in the fnc won't help because this check is done on entry to a function. This isn't null, but one of the members is.

Fix by moving the map out of the method and into the class as static. Then it will initialize on startup. Otherwise, you have to put a CR around the calls do DoStuff(). You can protect from the remaining MT issues by placing a CR around the use of the map itself (e.g. where DoStuff uses operator[]).

半暖夏伤 2024-07-14 10:14:40

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

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

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

mappedChars is static so it's shared by all the threads that execute DoStuff(). That alone could be your problem.

If you have to use a static map, then you may need to protect it with a mutex or critical section.

Personally, I think using a map for this purpose is overkill. I would write a helper function that takes a char and subtracts '0' from it. You won't have any thread safety issues with a function.

对你再特殊 2024-07-14 10:14:40

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

if (mappedChars.empty())

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

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

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

If multiple threads are invoking the function DoStuff this will mean that the initialization code

if (mappedChars.empty())

can enter a race condition. This means thread 1 enters the function, finds the map empty and begins filling it. Thread 2 then enters the function and finds the map non-empty (but not completely initialized) so merrily begins reading it. Because both threads are now in contention, but one is modifying the map structure (i.e inserting nodes), undefined behaviour (a crash) will result.

If you use a synchronization primitive prior to checking the map for empty(), and released after the map is guaranteed to have been completely initialized, all will be well.

I had a look via Google and indeed static initialization is not thread safe. Thus the declaration static mappedChars is immediately an issue. As others have mentioned it would be best if your initialization was done when only 1 thread is guaranteed to be active for the life time of initialization.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文