Нарушение считывания местоположения в std :: map operator [] - PullRequest
7 голосов
/ 24 октября 2008

Я столкнулся с проблемой при запуске старого кода, который был передан мне. Это работает в 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
}

Я уже пытался заморозить потоки в operator [] и пытаться заставить их все проходить через него одновременно, но я не смог воспроизвести исключение с помощью этой методологии.

Можете ли вы вспомнить какую-либо причину, по которой это могло бы произойти, и только иногда?

(Да, я знаю, что STL не является потокобезопасным, и мне нужно внести изменения здесь. Мне в основном интересно, ПОЧЕМУ я вижу поведение, которое я описал выше.)

В соответствии с запросом, здесь приведена дополнительная информация об исключении:
Необработанное исключение в 0x00639a1c (app.exe) в app15-51-02-0944_2008-10-23.mdmp: 0xC0000005: Местоположение чтения нарушения доступа 0x00000004.

Спасибо всем, кто предлагает решения проблем многопоточности, но этот вопрос не предназначен для решения. Да, я понимаю, что представленный код не защищен должным образом и является чрезмерным в том, что он пытается сделать. У меня уже есть исправление для этого реализовано. Я просто пытаюсь лучше понять, почему это исключение было выброшено для начала.

Ответы [ 5 ]

5 голосов
/ 24 октября 2008

Учитывая адрес «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) он перепрыгивает через код инициализации - замораживание потоков в fnc не поможет, поскольку эта проверка выполняется при входе в функцию. Это не ноль, но один из членов.

Исправлено путём перемещения карты из метода в класс как статического. Затем он будет инициализирован при запуске. В противном случае, вы должны поместить CR вокруг вызовов do DoStuff (). Вы можете защитить от оставшихся проблем MT, поместив CR вокруг использования самой карты (например, где DoStuff использует оператор []).

3 голосов
/ 24 октября 2008

mappedChars является статическим, поэтому он используется всеми потоками, выполняющими DoStuff (). Это само по себе может быть вашей проблемой.

Если вам нужно использовать статическую карту, то вам может потребоваться защитить ее мьютексом или критическим разделом.

Лично я думаю, что использовать карту для этой цели - это излишне. Я написал бы вспомогательную функцию, которая берет char и вычитает из него '0'. У вас не возникнет проблем с безопасностью потоков при работе с функцией.

2 голосов
/ 24 октября 2008

Если несколько потоков вызывают функцию DoStuff, это будет означать, что код инициализации

if (mappedChars.empty())

может войти в состояние гонки. Это означает, что поток 1 входит в функцию, находит карту пустой и начинает ее заполнять. Затем поток 2 входит в функцию и находит карту непустой (но не полностью инициализированной), поэтому весело начинает читать ее. Поскольку оба потока в настоящее время находятся в конфликте, но один из них изменяет структуру карты (т.е. вставляет узлы), результатом будет неопределенное поведение (сбой).

Если вы используете примитив синхронизации до проверки карты на empty() и выпущены после того, как карта гарантированно будет полностью инициализирована, все будет хорошо.

Я посмотрел через Google , и действительно статическая инициализация не поточно-безопасна. Таким образом, объявление static mappedChars сразу же становится проблемой. Как уже упоминали другие, было бы лучше, если бы ваша инициализация была выполнена, когда гарантированно активен только 1 поток в течение срока службы инициализации.

1 голос
/ 24 октября 2008

Когда вы вступаете в многопоточность, обычно слишком много происходит, чтобы точно определить точное место, где дела идут плохо, поскольку оно всегда будет меняться. Есть множество мест, где использование статической карты в многопоточной ситуации может испортиться.

См. этот поток , где описаны некоторые способы защиты статической переменной. Лучше всего будет вызвать функцию один раз, прежде чем запускать несколько потоков для ее инициализации. Либо так, либо переместите статическую карту наружу и создайте отдельный метод инициализации.

0 голосов
/ 24 октября 2008

Вы когда-нибудь звоните operator[] с аргументом, который не находится в диапазоне 0..9? Если это так, то вы непреднамеренно изменяете карту, что, вероятно, приводит к тому, что в других потоках происходит сбой. Если вы вызываете operator[] с аргументом, которого еще нет на карте, он вставляет этот ключ в карту со значением, равным значению по умолчанию для типа значения (0 в случае int).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...