Чуть не рехнулась, пока искала эту багу! Вроде всё правильно, а лезет эксепшн...
Использую MSVS 2005 SP1 и boost 1.44.0.
Суть в том, что мне нужен был дек(std::deque) для буферизации данных между несколькими потоками. В этот дек пишут несколько потоков и есть один читающий поток, который периодически обрабатывает данные и сбрасывает содержимое дека.
Чтобы не тормозить работу с деком, была огранизована система работы с указателями:
- существует указатель на объект дека (он инициализируется в начале работы программы, до запуска всех потоков)
- пишущие потоки добавляют данные, используя этот указатель
- читающий поток подменяет глобальный указатель на новый созданный объект , сбрасывает накопленное содержимое и затем освобождает использованный объект
Естественно, всё это происходит под мьютексами и собирается как многопоточное приложение.
Сначала были заюзаны локи boost:mutex::scoped_lock. Я их обычно использую где ни попадя: удобны, безопасны для эксепшнов и т.п. Но вот тут и оказалась засада: при нескольких пишущих потоках вылезал эксепшн нарушения стека. Позже при тестировании выяснилось, что та же беда случается и при нескольких читающих потоках, даже при одном пишушем. Я уже думала, что у меня крыша поехала: ну нет ничего подозрительного, всё под мьютексами... потом решила наобум заменить scoped_lock на простые lock/unlock... и оно заработало!!!
Написала тестовую софтинку, бага(нарушение стека) отчётливо проявляется при установке макросов NUMBER_OF_WRITERS_1 или NUMBER_OF_WRITERS_1 в число больше единицы. При этом NUMBER_OF_WRITERS_2 и NUMBER_OF_READERS_2 могут быть абсолютно любыми - никаких проблем не возникает.
#include <deque>
#include <boost/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread/barrier.hpp>
using namespace std;
using namespace boost;
// число потоков со scoped_lock
// тест падает, если хотя бы один из них больше 1
#define NUMBER_OF_WRITERS_1 1
#define NUMBER_OF_READERS_1 1
// число потоков с lock/unlock
// может быть любое число потоков
#define NUMBER_OF_WRITERS_2 2
#define NUMBER_OF_READERS_2 2
typedef deque<long> deq_type;
// указатель на общий дек
deq_type *pdeq;
// мьютекс для защиты дека
mutex mt;
// барьеры используются для одновременного старта всех потоков
barrier barr1(NUMBER_OF_WRITERS_1+NUMBER_OF_READERS_1);
barrier barr2(NUMBER_OF_WRITERS_2+NUMBER_OF_READERS_2);
// флаг завершения работы потоков
bool stop_flag;
// заглушка
void dummy()
{
}
// пишуший поток со scoped_lock
void writer1()
{
barr1.wait();
while(!stop_flag)
{
{
mutex::scoped_lock(mt);
pdeq->push_front(1);
}
this_thread::sleep(posix_time::milliseconds(10));
}
}
// читающий поток со scoped_lock
void reader1()
{
deq_type *pcopy;
barr1.wait();
while(!stop_flag)
{
if(pdeq->size() > 0)
{
{
mutex::scoped_lock(mt);
pcopy = pdeq;
pdeq = new deq_type();
}
deque<long>::iterator i;
for(i=pcopy->begin(); i!=pcopy->end(); i++)
{
dummy();
}
pcopy->clear();
delete pcopy;
}
}
this_thread::sleep(posix_time::milliseconds(10));
}
// пишуший поток со lock/unlock
void writer2()
{
barr2.wait();
while(!stop_flag)
{
mt.lock();
pdeq->push_front(1);
mt.unlock();
this_thread::sleep(posix_time::milliseconds(10));
}
}
// читающий поток со lock/unlock
void reader2()
{
deq_type *pcopy;
barr2.wait();
while(!stop_flag)
{
if(pdeq->size() > 0)
{
mt.lock();
pcopy = pdeq;
pdeq = new deq_type();
mt.unlock();
deque<long>::iterator i;
for(i=pcopy->begin(); i!=pcopy->end(); i++)
{
dummy();
}
pcopy->clear();
delete pcopy;
}
}
this_thread::sleep(posix_time::milliseconds(10));
}
int main()
{
thread_group tg;
// тест со scoped_lock
// инициализируем дек
pdeq = new deq_type();
stop_flag = false;
for(int i=0; i<NUMBER_OF_WRITERS_1; i++) tg.create_thread(writer1);
for(int i=0; i<NUMBER_OF_READERS_1; i++) tg.create_thread(reader1);
this_thread::sleep(posix_time::seconds(1));
stop_flag = true;
tg.join_all();
if(pdeq != 0)
{
pdeq->clear();
delete pdeq;
}
// тест с lock/unlock
// инициализируем дек
pdeq = new deq_type();
stop_flag = false;
for(int i=0; i<NUMBER_OF_WRITERS_2; i++) tg.create_thread(writer2);
for(int i=0; i<NUMBER_OF_READERS_2; i++) tg.create_thread(reader2);
this_thread::sleep(posix_time::seconds(1));
stop_flag = true;
tg.join_all();
if(pdeq != 0)
{
pdeq->clear();
delete pdeq;
}
return 0;
}
а если ради эксперимента написать свою следилку за разлочиванием - в конструкторе принимает указатель на синхро-объект, а в деструкторе разлочивает
Если всё будет в порядке, то глюк действительно в scoped_lock имеется
чёрт, да что сегодня за день за такой?
стала проверять под MSVS 2010, а там вообще boost::thread падает с access violation в конструкторе!
буст тот же, 1.44.0.
причём на StackOverflow пишут, что это бага 43-го буста и что она даже была пофиксена (http://stackoverflow.com/questions/2914666/boost-thread-throws-bad-alloc-exception-in-vs2010), но вот у меня прецедент с 44-м бустом. и в списках багов буста я вроде не нашла этой баги... пороюсь в мэйл-рассылке буста, может там есть что-то насчёт этого.
версия MSVS такая:
Version 10.0.30319.1 RTMRel
Installed Version: Premium
ну и что теперь делать?
дома как раз ставлю новый 64-битный экспериментальный деб, там ваще всё пока сыро и поди тоже полезет какая-нибудь шняга...
пока неясно, куда сообщать о проблеме. буду ещё экспериментировать.
ВНИМАНИЕ, БАГА В MS STL!
Подсказали мне в листе рассылок буста, в чём проблема с deque и многопоточностью (http://groups.google.com/group/boost-list/msg/c349f5f9024493ef):
https://connect.microsoft.com/VisualStudio/feedback/details/533131/bug-in-std-deque-non-conforming-invalidation-of-iterators-after-pop-front?wa=wsignin1.0
Вкратце: не юзайте std::deque в MSVS начиная с 2005 и заканчивая 2010. Метод pop_front() иногда даёт сбой и портит итераторы элементов, к которым он не имеет никакого отношения. А в моём случае то же самое делал push_front() . Одного поля ягодки! Видимо, проявление баги зависит от таймингов: когда начинается частое обращение (даже защищённое мьютексами!) - что-то кривеет и некоторые итераторы становятся невалидными.
Мелкософт обещает стать хорошим и исправить багу в студии 2011. А пока что лучше не рисковать и вообще не юзать std::deque.
А вот с конструктором boost::thread пока неясно...
P.S. Гнусности ещё только начинаются! Теперь для начала нужно пересобрать стопицот старых, но вполне используемых проектов, чтобы избавиться от использования std::deque (а сначала ещё надо найти, на что бы такое его заменить!). Но кроме этого я вдруг обнаружила, что исходники boost также содержат этот злополучный deque и некоторые библиотеки потенциально опасны при работе под компилером MSVС версий от 7-й до 10-й. В частности, deque используется в исходниках бустовских библиотек smart_ptr (sp_collector.cpp), boost:expressive и boost::regex (command_line.cpp). И в заголовочниках некоторых библиотек буста есть #include <deque>.... Одним словом, приличная такая "жожоба", как мы выражались на одной работе.
Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)