Привет всем!
Читал я дискуссию по поводу потокобезопасности vector-а и решил привести реальный пример, когда vector съезжает.
Ситауация такая:
Есть два потока, в одном кусок кода который пишет в вектор выглядит так:
singleLock.Lock();
m_Vector.push_back(/* некое значение */);
singleLock.Unlock();
sinleLock объявлен до начала цикла так:
CMyApp* pApp = (CMyApp*) AfxGetApp();
CSingleLock singleLock(&pApp->m_critSection);
Критикал секшн объявлена в объекте приложения (для простоты) так:
public:
CCriticalSection m_critSection;
Вектор объявлен во втором потоке:
vector<pair<basic_string<T>, LONG> > m_Vector;
В первый поток передается ссылка на него (или указатель).
Во втором потоке должен до завершения (т.е. до достижения vector.end()) отработать тестовый цикл:
vector<pair<basic_string<T>, LONG> >::iterator I;
CMyApp* pApp = (CMyApp*) AfxGetApp();
CSingleLock singleLock(&pApp->m_critSection);
long i = 0;
for(I = m_Vector.begin(); I < m_Vector.end(); I++)
{
singleLock.Lock();
TRACE("Index = %i\n", i);
i++;
singleLock.Unlock();
}
ЧТО ЖЕ происходит НА САМОМ ДЕЛЕ?
Итератор I указывает на несуществующее значение и при обращении (*m_Vector).first получаем ошибку и МАЛО ТОГО, цикл спокойно крутится до значений i гораздо больших чем m_Vector.size() — 1.
ВСЕ ЭТО РАБОТАЕТ только в том случае, если написать цикл иначе:
while(i < m_Vector.size())
{
//повторяем то же самое что и выше, только для поучения значений используем оператор
//m_Vector[i].first и m_Vector[i].second.
}
Кто-нибудь понимает почему съезжает итератор?
(Ооооочееннннь не хочется переделывать все потоки содержащие такой цикл (больно их много).)
Как все-таки заставить эту фигню правильно работать?
Векто съезжает из-за того, что при добавлении айтемов в вектор тот может производит реаллокацию своего буфера. При этом полученные ранее итераторы становятся невалидными.
У тебя один поток добавляет айтем, другой — использует итератор, который после добавления становится невалидным.
Здравствуйте, rus blood, Вы писали:
RB>Здравствуйте, RealBobEx, Вы писали:
RB>Векто съезжает из-за того, что при добавлении айтемов в вектор тот может производит реаллокацию своего буфера. При этом полученные ранее итераторы становятся невалидными.
RB>У тебя один поток добавляет айтем, другой — использует итератор, который после добавления становится невалидным.
Хммм... В таком случае должно помогать простое обновление значений итераторов, например так:
singleLock.Lock();
I = m_Vector.begin();
End = m_Vector.end();
singleLock.Unlock();
long i = 0;
for(I; I < End; )
{
singleLock.Lock();
//Используем I, т.к. теперь он валидный.
i++;
I++;
End = m_Vector.end();
singleLock.Unlock();
}
Однако это помогает только не выйти за верхнюю границу, но правильные значения все равно не получаются.
RBE>Хммм... В таком случае должно помогать простое обновление значений итераторов, например так: RBE>
RBE> singleLock.Lock();
RBE> I = m_Vector.begin();
RBE> End = m_Vector.end();
RBE> singleLock.Unlock();
RBE> long i = 0;
RBE> for(I; I < End; )
RBE> {
RBE> singleLock.Lock();
RBE> //Используем I, т.к. теперь он валидный. <- да не фига он не валидный. Про него речь-то !!!
RBE> i++;
RBE> I++;
RBE> End = m_Vector.end();
RBE> singleLock.Unlock();
RBE> }
RBE>
Здравствуйте, rus blood, Вы писали:
RB>Итератор I остается старым -> инвалидируется...
Т.е. Вы утверждаете, что синхронизация в данном случае невозможна?
Так же думает и Psyton.
Ну а зачем тогда объекты синхронизации? Они — то как раз и имеют цель предотвратить
доступ к вектору когда идет запись в него, иначе говоря предназначенны сделать использоване обычной коллекции потоко-безопасным.
Если бы Ваше предполжение о перераспределении памяти было верно, то был бы нерабочим элементарный код:
vector<int> v;
vector<int>::iterator I;
for(i = 0; i < 10000; i++)
v.push_back(1);
for(I = v.begin(); I < v.end(); I++)
//используем I.for(i = 0; i < 10000; i++)
v.push_back(2); //Память перераспередлилась.for(I = v.begin(); I < v.end(); I++)
//используем I. <<тут была бы ошибка. А это, согласитесь, мягко говоря не так.
Я предположил другую причину.
И нашел решение которое реально заработало в моем коде.
Не изобретай лишних сущностей без крайней необходимости!
R> vector<int> v;
R> vector<int>::iterator I;
R> for(i = 0; i < 10000; i++)
R> v.push_back(1);
R> for(I = v.begin(); I < v.end(); I++)
R> //используем I.
R> for(i = 0; i < 10000; i++)
R> v.push_back(2); //Память перераспередлилась.1. Тот I, который был в первом цикле, стал невалидным.
R> for(I = v.begin(); I < v.end(); I++)
R> //используем I. <<тут была бы ошибка. А это, согласитесь, мягко говоря не так.2. Тут не будет ошибки. Ведь Вы же проинициализировали I заново, вызвав begin() в начале цикла!!!
R>
Что-то Вы похоже запутались в итераторах. Сначала пишете неверную конструкцию, и считаете, что она будет работать верно. Потом Вы пишете верный код и ждете ошибку...
Попробуйте для сравнения вот так —
vector<int> v;
vector<int>::iterator I;
for(i = 0; i < 10000; i++)
v.push_back(1);
for(I = v.begin(); I < v.end(); I++)
//используем I. I = v.begin(); // <- выставляем I в начало вектора перед повторным распределением...for(i = 0; i < 10000; i++)
v.push_back(2); //Память перераспередлилась.for(; I < v.end(); I++) // <- начинаем цикл с текущего значения итератора...
//используем I. <<тут была бы ошибка. А это, согласитесь, мягко говоря не так.