Кейс на многопоточность (C++11): std::vector v; std::thread t1([&]{for(int i=0;i<1000;++i)v.push_back(i);}); std::thread t2([&]{for(int i=0;i

5 Фев в 11:51
13 +1
0
Ответы
1
Коротко — код вызывает неопределённое поведение: одновременные вызовы `v.push_back` не потокобезопасны (гонки данных, одновременная реаллокация буфера, повреждение памяти, invalidation итераторов и т.п.). Ниже — проблемы, способы исправления и альтернативы.
Проблемы
- Доступ без синхронизации к общему состоянию `std::vector` → data race → undefined behavior.
- Одновременная реаллокация внутреннего буфера при наполнении → повреждение памяти.
- Итераторы/указатели вектора могут быть инвалидаированы при реаллокации.
Как исправить (варианты)
1) Простейший — мьютекс
- Гарантирует корректность, но вызывает блокировку при каждой вставке.
Пример:
std::mutex m;
std::thread t1([&]{ for(int i=0;i<100010001000;++i){ std::lock_guard g(m); v.push_back(i); } });
std::thread t2([&]{ for(int i=0;i<100010001000;++i){ std::lock_guard g(m); v.push_back(i); } });
2) Зарезервировать место + атомарный индекс (без блокировок на вставке)
- Выделить память заранее: `v.resize(200020002000);` или `v.reserve(200020002000);` и затем писать в разные ячейки по атомарному индексу.
Пример (без realoc, безопасно):
v.resize(200020002000);
std::atomic idx{0};
std::thread t1([&]{ for(int i=0;i<100010001000;++i){ size_t pos = idx.fetch_add(1); v[pos] = i; } });
... // t2 аналогично
3) Разделить диапазоны заранее (deterministic partitioning)
- Если заранее известно, что каждый поток запишет ровно NNN элементов, сделать `v.resize(num_threads * N)` и дать каждому потоку свой диапазон `v[offset .. offset+N-1]`. Без синхронизации.
4) Буферы на поток + объединение
- Каждый поток собирает в свой `std::vector`, затем один поток (или в защищённом участке) сливает результаты в общий вектор:
thread-local v1, v2; потом
{
std::lock_guard g(m);
v.insert(v.end(), v1.begin(), v1.end());
v.insert(v.end(), v2.begin(), v2.end());
}
Альтернативы блокировкам (lock-free / concurrent контейнеры)
- Использовать специализированные многопоточные контейнеры: Intel TBB `tbb::concurrent_vector`, moodycamel::ConcurrentQueue, folly::AtomicUnboundedQueue и т.п. Они реализуют безопасную параллельную вставку.
- lock-free структуры (кольцевые буферы с атомарным tail/head) — подходит, если API позволяет (FIFO очередь и т.д.).
- Использовать std::atomic и предвыделение/индексацию (см. пункт 2) — простая lock-free стратегия для однонаправленной записи.
Замечания
- Если используете `reserve` (а не `resize`), нельзя писать через `operator[]` за пределами существующих элементов — нужно `resize` или аккуратно управлять "занятостью" символов.
- При использовании lock-free решений учитывать требования к порядку элементов и исключениям при аллокации.
- Выбор зависит от требований: простота → мьютекс; производительность и меньшая блокировка → thread-local + merge или предвыделение + atomic index; готовые контейнеры → TBB/folly/моodycamel.
Конкретный быстрый рецепт для данного примера:
- Самый простой корректный: взять `std::mutex` вокруг `push_back`.
- Более эффективный: `v.resize(200020002000); std::atomic idx{0};` и записывать по `v[idx.fetch_add(1)]`.
5 Фев в 12:00
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир