Существуют ли параллельные контейнеры в C++11?
В частности, я ищу блокирующую очередь. Есть ли такая вещь в C++11? Если нет, то каковы мои другие варианты? Я действительно больше не хочу спускаться на уровень нитей. Слишком подвержен ошибкам.
5 ответов:
По словам Диего Dagum от Майкрософт Visual С++ Команда:
Повторяющийся вопрос (Ну, один из многих) касается контейнеров STL и являются ли они потокобезопасными.
Принимая слова Стефана здесь, реальность такова, что они не являются, не как ошибка, но как особенность: наличие каждой функции-члена каждого STL контейнер, получивший внутренний замок, уничтожил бы производительность. Как универсальная, очень многоразовая библиотека, на самом деле это не так обеспечить правильность либо: правильный уровень для размещения блокировок является определяется тем, что делает программа. В этом смысле индивидуальное функции-члены, как правило, не имеют такого правильного уровня.
Библиотека параллельных шаблонов (PPL) включает несколько контейнеров, обеспечивающих потокобезопасный доступ к своим элементам:
- классconcurrent_vector - это класс контейнера последовательности, который позволяет произвольный доступ к любому элементу. Он позволяет добавлять приложения, безопасные для параллелизма, операции доступа к элементам, доступа к итераторам и обхода итераторов.
- классconcurrent_queue -это класс контейнера последовательностей, который позволяет первому входящему и первому выходящему доступу к его элементам. Это позволяет ограниченному набору безопасных для параллелизма операций, таких как push и try_pop, назвать несколько.
Некоторые примеры здесь.
Тоже интересно: http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html.
C++11 не предоставляет параллельных контейнеров сам по себе. Однако есть и библиотечные варианты. Кроме уже упомянутого PPL, не забудьте библиотеку Intel TBB.
Он имеет параллельный
queue
,hash_map
,set
иvector
реализация. Но это не только потокобезопасная контейнерная библиотека, она также поставляется с параллельной версией стандартных алгоритмов (for-loop, reduce, sort,...).
Я удивляюсь, что никто не упомянул moodycamel::коллекции concurrentqueue. Мы используем его уже довольно давно, и он работает очень хорошо. Характерно, что его реализация не блокируется, что сразу же приносит огромную скорость. Другие причины его использования (цитата с официального сайта):
Существует не так уж много полноценных незащищенных очередей для C++. Повышать имеет один, но он ограничен объектами с тривиальными операторами присваивания и тривиальные деструкторы, например. Очередь ТББ Intel не является без блокировки и требует тривиальных конструкторов. Их очень много научные статьи, реализующие свободные от блокировки очереди в C++, но пригодные для использования исходный код трудно найти, а тесты и подавно.
Некоторые критерии и сравнения доступны здесь , здесь и здесь.
Интерфейсы контейнеров просто не были разработаны с этой целью. Для интерфейсов, которые они используют, блокировка, видимая клиенту, действительно является единственным способом, которым вы можете достичь этого, гарантируя правильность и предсказуемое поведение. Это также было бы ужасно неэффективно, потому что количество приобретений было бы очень высоким (относительно хорошей реализации).
Решение 1
Передать по значению (если применимо).
Решение 2
Создайте коллекцию простых реализаций bolt-on, которые можно использовать для передачи контейнеров, удерживая блокировку области видимости (рассмотрим ее псевдо c++):
template <typename TCollection> class t_locked_collection { public: t_locked_collection(TCollection& inCollection, t_lock& lock) : collection(inCollection), d_lock(lock), d_nocopy() { } TCollection& collection; // your convenience stuff private: t_scope_lock d_lock; t_nocopy d_nocopy; };
Затем вызывающий связывает блокировку с коллекцией, а затем вы обновляете свои интерфейсы, чтобы использовать (передавать) тип контейнера, где это уместно. Это просто расширение класса бедняков.
Этот запертый контейнер является одним из простых примеров, и есть несколько других вариантов. Это маршрут, который я выбрал, потому что он действительно позволяет использовать уровень детализации, который идеально подходит для вашей программы, даже если он не так прозрачен (синтаксически), как заблокированные методы. Также относительно легко адаптировать существующие программы. По крайней мере, он ведет себя предсказуемым образом, в отличие от коллекций с внутренними замками.Другой вариант:
template <typename TCollection> class t_lockable_collection { public: // ... private: TCollection d_collection; t_mutex d_mutex; }; // example: typedef t_lockable_collection<std::vector<int> > t_lockable_int_vector;
...где тип, подобный
t_locked_collection
, может использоваться для представления базовой коллекции. Не подразумевая, что подход является надежным, просто дурак устойчив.
Моя версия параллельной неупорядоченной карты параллелизм пространства имен {
template<typename T,typename T1> class unordered_bucket: private std::unordered_map<T,T1> { mutable std::recursive_mutex m_mutex; public: T1 &operator [](T a) { std::lock_guard<std::recursive_mutex> l(m_mutex); return std::unordered_map<T,T1>::operator [](a); } size_t size() const noexcept { std::lock_guard<std::recursive_mutex> l(m_mutex); return std::unordered_map<T,T1>::size(); } vector<pair<T,T1>> toVector() const { std::lock_guard<std::recursive_mutex> l(m_mutex); vector<pair<T,T1>> ret; for(const pair<T,T1> &p:*this) { ret.push_back(p); } return ret; } bool find(const T &t) const { std::lock_guard<std::recursive_mutex> l(m_mutex); if(this->std::unordered_map<T,T1>::find(t) == this->end()) return false; //not found return true; } void erase() { std::lock_guard<std::recursive_mutex> l(m_mutex); this->unordered_map<T,T1>::erase(this->begin(),this->end()); } void erase(const T &t) { std::lock_guard<std::recursive_mutex> l(m_mutex); this->unordered_map<T,T1>::erase(t); } }; #define BUCKETCOUNT 10 template<typename T,typename T1> class ConcurrentMap { std::vector<unordered_bucket<T,T1>> m_v; public: ConcurrentMap():m_v(BUCKETCOUNT){} //using 10 buckets T1 &operator [](T a) { std::hash<T> h; return m_v[h(a)%BUCKETCOUNT][a]; } size_t size() const noexcept { size_t cnt=0; for(const unordered_bucket<T,T1> &ub:m_v) cnt=cnt+ub.size(); return cnt; } vector<pair<T,T1>> toVector() const { vector<pair<T,T1>> ret; for(const unordered_bucket<T,T1> &u:m_v) { const vector<pair<T,T1>> &data=u.toVector(); ret.insert(ret.end(),data.begin(),data.end()); } return ret; } bool find(const T &t) const { for(const unordered_bucket<T,T1> &u:m_v) if(true == u.find(t)) return true; return false; } void erase() { for(unordered_bucket<T,T1> &u:m_v) u.erase(); } void erase(const T &t) { std::hash<T> h; unordered_bucket<T,T1> &ub = m_v[h(t)%BUCKETCOUNT]; ub.erase(t); } }; }