Является ли std::deque действительно потокобезопасным?

Я знаю, что книги говорят о том, что std::deque является умеренно потокобезопасным, но мой опыт доказывает обратное. Я использую VS 2010. Есть как минимум два потока (может быть N потоков, но добавление потоков только ускорит возникновение проблемы), каждый из которых выполняет один и тот же код. Каждый поток содержит один и тот же код, однако указатель на уникальный экземпляр структуры, содержащей двухстороннюю очередь, передается каждому потоку, поэтому теоретически у каждого из них есть своя двухсторонняя очередь для работы. Однако в разное время я получаю ошибки, когда потоки пытаются получить доступ к очереди (всегда чтение). Дека определяется примерно так:

struct A
{
   deque<TAS*> dqTas;
}

TAS — это указатель на другую структуру.

Структура A создается как

A* Aptr = new A;  

Структура TAS также создается таким же образом.

TAS* pTas = new TAS

Ошибки характеризуются:

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

2) Чем больше потоков, тем быстрее проблема возникает. Проблема никогда не возникает с 1 потоком.

3) Сообщения об ошибках различаются, говоря, что двухсторонняя очередь не может быть разыменована или индекс выходит за рамки. Если дек не может разыменовать ошибку, то причина, по которой проверка данных полностью необнаружима. Все выглядит в порядке, указатели, существующие данные в двухсторонней очереди и т. д. Если проблема заключается в том, что индекс выходит за пределы диапазона, то каким-то образом один или несколько элементов данных (из нескольких сотен) внезапно повреждаются в самой двухсторонней очереди.

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

Кажется, единственное, что могло вызвать это, — это глобальный счетчик или указатель в коде std::deque. Характер этих ошибок указывает на источник конфликта потоков. Я даже проверил, что адреса каждого экземпляра структуры разные. Теоретически вероятность коллизий должна быть нулевой, поскольку каждый поток имеет свою собственную копию очереди. Единственный способ, которым это происходит с этой настройкой, - это если в коде std::deque есть глобальный указатель или счетчик.

У кого-нибудь еще был такой опыт? Будут ли функции boost deque работать лучше в таком сценарии?

Если вам интересно, это код, который gpfs:

pTs->dqTas.push_front( pTb );    <<GPF happens after a write

#if defined (DEBUG)
    long d2 = pTs->dqTas.size()-1;
    if( d2 > 0 )
    {
        TASBAR* pDel2;
        //pDel2 = pTs->dqTas[d2];
        pDel2 = pTs->dqTas.at(d2);   //<<GPF happens here
    }
#endif

РАЗРЕШАЮЩАЯ СПОСОБНОСТЬ:

Спасибо всем за ваши комментарии. Проблема была решена, и она не имела ничего общего с контейнером deque. Случайное повреждение дека было симптомом проблемы. Проблема была вызвана некоторыми старыми статическими переменными, объявленными локально в функции класса, созданного потоком. Эти переменные перезаписывались адресами других объектов, сохраняемых в той же двухсторонней очереди в другом потоке. Я просто избавился от них, и все заработало, как ожидалось. Как бы элементарно это ни звучало, урок, который следует помнить, заключается в том, что статические переменные по существу являются глобальными переменными (даже если они определены локально внутри функции) между потоками. Вероятно, лучше вообще избегать их в любом коде, который входит в потоки, которые могут запускать несколько экземпляров одного и того же кода, если только не очень ясно, почему и как они используются.


person brimaa    schedule 09.04.2014    source источник
comment
«умеренно потокобезопасный»? Разве это не другой способ сказать «не потокобезопасный»? Безопасность потоков - это не то, что вы можете сделать наполовину...   -  person Jeremy Friesner    schedule 10.04.2014
comment
Я не вижу ничего, что защищало бы эту структуру или эту деку от проблем параллелизма. Если у вас есть модуль записи (а он у вас есть), вы должны обеспечить защиту параллелизма в той или иной форме как на стороне записи, , так и на стороне чтения. Как вы решите это сделать, полностью зависит от вас, но, поскольку опубликованный некомпилируемый код не имеет даже намека на многопоточность или попытки защиты параллелизма, вы просите нас помочь вам решить проблему, в которой мы буквально см. нет.   -  person WhozCraig    schedule 10.04.2014
comment
Автор утверждает, что у каждого потока есть свой отдельный экземпляр.   -  person i_am_jorf    schedule 10.04.2014
comment
Можете ли вы показать нам, как вы создаете deque для каждого потока?   -  person i_am_jorf    schedule 10.04.2014
comment
@jeffamaphone Я тоже не куплюсь на это, пока не увижу. Это возможно, даже если итератор или индекс из одного контейнера используются против другого (что, очевидно, было бы плохо). но по крайней мере .at() там, чтобы ловить выход за пределы поля, а не незащищенный [], так что мы получили это, чтобы помочь.   -  person WhozCraig    schedule 10.04.2014
comment
Вы говорите, что нет шанса, что какой-либо поток имеет какой-либо указатель или ссылку любого рода на вашу структуру или дек внутри (включая итераторы), к которому когда-либо обращаются разные потоки ?   -  person WhozCraig    schedule 10.04.2014
comment
Спасибо за все ваши ответы. Каждый поток получает свою собственную копию двухсторонней очереди, созданной с помощью новой, именно поэтому она будет потокобезопасной. Не должно быть столкновений. Я пробую это с С++ 11 и смотрю, исчезнет ли проблема. WhozCraig, дай мне подумать об этом. См. выше, как создается двухсторонняя очередь.   -  person brimaa    schedule 10.04.2014
comment
Если возможно, попытайтесь разобрать это до голых костей, потому что, если это так, как вы говорите, этого просто не должно происходить. Меня... беспокоит... динамическая природа элементов, поскольку вы делаете копию двухсторонней очереди, вы делаете копии всех этих указателей, а не объектов, на которые они указывают, но похоже, что вы уже можете знайте это, и это предусмотрено дизайном (и, конечно же, эти объекты также должны быть потокобезопасными).   -  person WhozCraig    schedule 10.04.2014
comment
Я хотел бы увидеть эти книги.   -  person StackedCrooked    schedule 10.04.2014
comment
stackoverflow.com/ вопросы/4105930/   -  person brimaa    schedule 10.04.2014
comment
Я тоже этого не понимаю. Если каждый поток получает свой собственный экземпляр deque, и только этот поток читает/записывает в этот экземпляр (т. е. deque не используется для связи между потоками), я не вижу, чтобы «потокобезопасность» была бы даже соображение. Экземпляры объектов в деке могут быть проблемой, но это не проблема дека:)   -  person Martin James    schedule 10.04.2014


Ответы (1)


Как правило, ни один контейнер не является потокобезопасным. Это просто контейнер. Я бы порекомендовал вам сделать его потокобезопасным самостоятельно. Создание объекта std::lock_guard с std::mutex в стеке сделает ваш код потокобезопасным. Надеюсь, это поможет: ниже пример кода:

std::mutex lockMutex;
std::lock_guard<std::mutex> lock(lockMutex);
person aalimian    schedule 10.04.2014
comment
Спасибо, я попробую это. Когда вы говорите поместить его в стек, вы имеете в виду окружать каждое чтение/запись в очередь с помощью этого кода или в структуре, где определена очередь? Я был бы немного обеспокоен хитом производительности. Но я должен увидеть. - person brimaa; 10.04.2014
comment
Создать в стеке означает не создавать его заново, таким образом вы будете знать, что блокировка будет уничтожена после выхода вашей функции. Просто используйте код так, как он есть, и все будет в порядке. - person aalimian; 10.04.2014