Использую 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;
}
Вроде ошибок я не вижу. Не первый год пишу многопоточные приложения... То ли баг буста, то ли STL. Нужно дополнительно проверять. Завтра соберу буст под MSVS 2010 и проверю. А дома под линём проверю...
Может, кто-то сталкивался с подобной хренью?
P.S. модераторы, поправьте в названии stl::deque на std::deque
P.P.S чуть подправила код (быстро копировала, забыла вторую инициализацию), но суть баги не меняется.