Класс Threadsafe Vector для C++
Кто-нибудь знает быстрый и грязный потокобезопасный векторный класс для C++? Я многопоточность некоторого кода, и я считаю, что проблема у меня связана с тем, как используются векторы. Я планирую переписать код, но перед тем, как сойти с ума, переделывая код, я бы хотел проверить его с помощью поточно-безопасного вектора. Я также полагаю, что если такая вещь существует, это было бы намного проще, чем писать свою собственную версию.
6 ответов
Это сложно из-за алгоритмов.
Предположим, вы обернули вектор так, чтобы все его функции-члены сериализовались с использованием мьютекса, как Java-синхронизированные методы. Затем одновременные звонки std::remove
на этом векторе все еще было бы небезопасно, потому что они полагаются на просмотр вектора и изменение его в зависимости от того, что они видят.
Таким образом, ваш LockingVector должен будет специализировать каждый шаблон в стандартных алгоритмах, чтобы обойти все это. Но тогда другие алгоритмы, такие как std::remove_if
будет вызывать пользовательский код под замком. Это незаметно делает за кулисами рецепт блокировки инверсии, как только кто-то начинает создавать векторы объектов, которые сами внутренне блокируют все свои методы.
В ответ на ваш актуальный вопрос: извините, нет, я не знаю ни одного. Для быстрого теста того типа, который вам нужен, я рекомендую начать с:
template <typename T>
class LockedVector {
private:
SomeKindOfLock lock;
std::vector<T> vec;
};
Затем поместите его в качестве контейнера замены и начните реализовывать функции-члены (и определения-члены-члены, и операторы), пока он не скомпилируется. Вы заметите довольно быстро, если какой-либо ваш код использует итераторы для вектора таким образом, который просто невозможно сделать поточно-ориентированным изнутри, и при необходимости вы можете временно изменить вызывающий код в этих случаях, чтобы заблокировать вектор через публичные методы.
Вы можете проверить TBB (например, concurrent_vector). Я никогда не использовал его, хотя, честно говоря, я нахожу, что размещение объектов контроля области видимости легче (особенно, если вектор правильно инкапсулирован).
Я думаю, вам будет гораздо проще продолжать использовать std::vector, но для защиты одновременного доступа с использованием какого-либо мьютекса или другого объекта синхронизации операционной системы. Вы также определенно захотите использовать RAII, если вы используете мьютекс.
Как объясняет Скотт Мейерс в эффективной книге по STL, благодаря многопоточному контейнеру вы можете ожидать, что:
- Многократные чтения безопасны
- Многократные записи в разные контейнеры безопасны.
Это все. Вы не можете ожидать, что многие другие вещи, такие как множественные записи в один и тот же контейнер, будут поточно-ориентированными. Если это все, что вы хотите, то вы можете взглянуть на STLPort. Если нет, то единственный вариант, который я вижу, - это содержать вектор в классе, который синхронизирует доступ к вектору.
Если вы еще этого не сделали, подумайте об использовании concurrent_vector из библиотеки tbb. Векторы C++ STL не являются поточно-ориентированными, поэтому, если вы планируете модифицировать векторный ресурс из нескольких потоков, самое простое решение, которое я нашел, - это использовать concurrent_vector.
Я забыл, кто обсуждал это, но одна стратегия создания поточно-ориентированного контейнера заключается в следующем:
- Все открытые методы вашего класса должны блокировать вектор и возвращать логическое значение, если они преуспели (и они могут не преуспеть!). Так что вместо использования
f = myvec[i]
использоватьif (myvec.tryGet(i, &f)) {...}
и реализовать класс соответственно. - Не предоставляйте метод подсчета. Пользователи должны использовать итераторы для прохождения вектора.
Примечание: будьте осторожны с итерацией. Вы должны быть умны в поддержании никогда не сжимающегося вектора с помощью итераторов с проверкой границ, иначе у вас может быть код с ошибками типа переполнения буфера.
Хромой и простой способ обеспечить "потокобезопасный" вектор - просто взять стандартный вектор и заблокировать вектор в каждом методе. Но если вы сделаете это, вы все равно можете получить неработающий код (например, цикл с итерациями от 0 до vec.count может изменить счет во время итерации).
Вторым способом предоставления "поточно-безопасных" контейнеров является создание неизменяемых контейнеров (каждый метод возвращает новый контейнер. Это определенно обсуждалось Эриком Липпертом. Это C#, но в основном он легко переводится на код C++. Вам все равно потребуется блокируйте контейнер, когда вы его используете, но все страшные проблемы, связанные с переполнением буфера, когда итераторы ломаются и все остальное исчезают. Реализация неизменяемого контейнера, вероятно, относительно 2-й природы для тех, кто имеет опыт работы с функциональным программированием.