C++0x не имеет семафоров? Как синхронизировать потоки?
это правда, что C++0x придет без семафоров? Уже есть некоторые вопросы по переполнению стека относительно использования семафоров. Я использую их (семафоры posix) все время, чтобы поток ждал какого-то события в другом потоке:
void thread0(...)
{
doSomething0();
event1.wait();
...
}
void thread1(...)
{
doSomething1();
event1.post();
...
}
Если бы я сделал это с помощью мьютекса:
void thread0(...)
{
doSomething0();
event1.lock(); event1.unlock();
...
}
void thread1(...)
{
event1.lock();
doSomethingth1();
event1.unlock();
...
}
проблема: это некрасиво, и не гарантируется, что thread1 сначала блокирует мьютекс (учитывая, что один и тот же поток должен блокировать и разблокировать мьютекс, вы также не можете заблокировать event1 до начала thread0 и thread1).
Итак, поскольку boost также не имеет семафоров, каков самый простой способ достичь вышеуказанного?
9 ответов:
вы можете легко построить один из мьютекса и условной переменной:
#include <mutex> #include <condition_variable> class semaphore { private: std::mutex mutex_; std::condition_variable condition_; unsigned long count_ = 0; // Initialized as locked. public: void notify() { std::lock_guard<decltype(mutex_)> lock(mutex_); ++count_; condition_.notify_one(); } void wait() { std::unique_lock<decltype(mutex_)> lock(mutex_); while(!count_) // Handle spurious wake-ups. condition_.wait(lock); --count_; } bool try_wait() { std::lock_guard<decltype(mutex_)> lock(mutex_); if(count_) { --count_; return true; } return false; } };
основываясь на ответе"Максима Егорушкина", я попытался сделать пример в стиле C++11.
#include <mutex> #include <condition_variable> class Semaphore { public: Semaphore (int count_ = 0) : count(count_) {} inline void notify() { std::unique_lock<std::mutex> lock(mtx); count++; cv.notify_one(); } inline void wait() { std::unique_lock<std::mutex> lock(mtx); while(count == 0){ cv.wait(lock); } count--; } private: std::mutex mtx; std::condition_variable cv; int count; };
я решил написать самый надежный/общий семафор C++11, который я мог бы, в стиле стандарта столько, сколько я мог (Примечание
using semaphore = ...
, обычно вы просто используете имяsemaphore
похоже на обычное использованиеstring
неbasic_string
):template <typename Mutex, typename CondVar> class basic_semaphore { public: using native_handle_type = typename CondVar::native_handle_type; explicit basic_semaphore(size_t count = 0); basic_semaphore(const basic_semaphore&) = delete; basic_semaphore(basic_semaphore&&) = delete; basic_semaphore& operator=(const basic_semaphore&) = delete; basic_semaphore& operator=(basic_semaphore&&) = delete; void notify(); void wait(); bool try_wait(); template<class Rep, class Period> bool wait_for(const std::chrono::duration<Rep, Period>& d); template<class Clock, class Duration> bool wait_until(const std::chrono::time_point<Clock, Duration>& t); native_handle_type native_handle(); private: Mutex mMutex; CondVar mCv; size_t mCount; }; using semaphore = basic_semaphore<std::mutex, std::condition_variable>; template <typename Mutex, typename CondVar> basic_semaphore<Mutex, CondVar>::basic_semaphore(size_t count) : mCount{count} {} template <typename Mutex, typename CondVar> void basic_semaphore<Mutex, CondVar>::notify() { std::lock_guard<Mutex> lock{mMutex}; ++mCount; mCv.notify_one(); } template <typename Mutex, typename CondVar> void basic_semaphore<Mutex, CondVar>::wait() { std::unique_lock<Mutex> lock{mMutex}; mCv.wait(lock, [&]{ return mCount > 0; }); --mCount; } template <typename Mutex, typename CondVar> bool basic_semaphore<Mutex, CondVar>::try_wait() { std::lock_guard<Mutex> lock{mMutex}; if (mCount > 0) { --mCount; return true; } return false; } template <typename Mutex, typename CondVar> template<class Rep, class Period> bool basic_semaphore<Mutex, CondVar>::wait_for(const std::chrono::duration<Rep, Period>& d) { std::unique_lock<Mutex> lock{mMutex}; auto finished = mCv.wait_for(lock, d, [&]{ return mCount > 0; }); if (finished) --mCount; return finished; } template <typename Mutex, typename CondVar> template<class Clock, class Duration> bool basic_semaphore<Mutex, CondVar>::wait_until(const std::chrono::time_point<Clock, Duration>& t) { std::unique_lock<Mutex> lock{mMutex}; auto finished = mCv.wait_until(lock, t, [&]{ return mCount > 0; }); if (finished) --mCount; return finished; } template <typename Mutex, typename CondVar> typename basic_semaphore<Mutex, CondVar>::native_handle_type basic_semaphore<Mutex, CondVar>::native_handle() { return mCv.native_handle(); }
в соответствии с семафорами posix, я бы добавил
class semaphore { ... bool trywait() { boost::mutex::scoped_lock lock(mutex_); if(count_) { --count_; return true; } else { return false; } } };
и я предпочитаю использовать механизм синхронизации на удобном уровне абстракции, а не всегда копировать вставку сшитой версии с использованием более базовых операторов.
вы также можете проверить cpp11-on-multicore - Он имеет портативную и оптимальную реализацию семафора.
репозиторий также содержит другие лакомства для потоков, которые дополняют потоки c++11.
вы можете работать с мьютексы и условные переменные. Вы получаете эксклюзивный доступ с мьютексом, проверяете, хотите ли вы продолжить или нужно ждать другого конца. Если вам нужно подождать, вы ждете в состоянии. Когда другой поток определяет, что вы можете продолжить, он сигнализирует условие.
короткий пример в библиотеке boost::thread, которую вы, скорее всего, можете просто скопировать (библиотеки C++0x и boost thread очень похожи).
также может быть полезна обертка семафора RAII в потоках:
class ScopedSemaphore { public: explicit ScopedSemaphore(Semaphore& sem) : m_Semaphore(sem) { m_Semaphore.Wait(); } ScopedSemaphore(const ScopedSemaphore&) = delete; ~ScopedSemaphore() { m_Semaphore.Notify(); } ScopedSemaphore& operator=(const ScopedSemaphore&) = delete; private: Semaphore& m_Semaphore; };
пример использования в многопоточном приложении:
boost::ptr_vector<std::thread> threads; Semaphore semaphore; for (...) { ... auto t = new std::thread([..., &semaphore] { ScopedSemaphore scopedSemaphore(semaphore); ... } ); threads.push_back(t); } for (auto& t : threads) t.join();
Я нашел shared_ptr и weak_ptr, длинный со списком, сделал работу, в которой я нуждался. Моя проблема заключалась в том, что у меня было несколько клиентов, желающих взаимодействовать с внутренними данными хоста. Как правило, хост обновляет данные самостоятельно, однако, если клиент запрашивает его, хост должен прекратить обновление до тех пор, пока клиенты не получат доступ к данным хоста. В то же время клиент может запросить эксклюзивный доступ, чтобы ни другие клиенты, ни хост не могли изменять эти данные хоста.
как я это сделал был, я создал структуру:
struct UpdateLock { typedef std::shared_ptr< UpdateLock > ptr; };
каждый клиент будет иметь члена такого:
UpdateLock::ptr m_myLock;
тогда у хоста будет член weak_ptr для эксклюзивности и список weak_ptrs для неисключительных блокировок:
std::weak_ptr< UpdateLock > m_exclusiveLock; std::list< std::weak_ptr< UpdateLock > > m_locks;
есть функция для включения блокировки, и еще одна функция, чтобы проверить, если хост заблокирован:
UpdateLock::ptr LockUpdate( bool exclusive ); bool IsUpdateLocked( bool exclusive ) const;
Я проверяю блокировки в LockUpdate, IsUpdateLocked и периодически в процедуре обновления хоста. Тестирование на замок-это как просто, как проверка того, истек ли срок действия weak_ptr, и удаление любого истекшего из списка m_locks (я делаю это только во время обновления хоста), я могу проверить, пуст ли список; в то же время я получаю автоматическую разблокировку, когда клиент сбрасывает shared_ptr, на котором они висят, что также происходит, когда клиент автоматически уничтожается.
общий эффект заключается в том, что клиенты редко нуждаются в эксклюзивности (обычно зарезервированной только для дополнений и удалений), большую часть времени запрос на LockUpdate (false), то есть неисключительный, преуспевает до тех пор, пока (! m_exclusiveLock). И LockUpdate (true), запрос на эксклюзивность, преуспевает только тогда, когда оба (! m_exclusiveLock) и (m_locks.пустой.))(
очередь может быть добавлена для смягчения между эксклюзивными и неисключительными блокировками, однако до сих пор у меня не было конфликтов, поэтому я намерен подождать, пока это не произойдет, чтобы добавить решение (в основном, поэтому у меня есть реальное условие тестирования).
пока это работает ну для моих нужд; я могу себе представить необходимость расширить это, и некоторые проблемы, которые могут возникнуть при расширенном использовании, однако, это было быстро реализовать, и требуется очень мало пользовательского кода.
в случае, если кто-то заинтересован в атомной версии, вот реализация. Производительность, как ожидается, лучше, чем мьютекс и переменных состояния версии.
class semaphore_atomic { public: void notify() { count_.fetch_add(1, std::memory_order_release); } void wait() { while (true) { int count = count_.load(std::memory_order_relaxed); if (count > 0) { if (count_.compare_exchange_weak(count, count-1, std::memory_order_acq_rel, std::memory_order_relaxed)) { break; } } } } bool try_wait() { int count = count_.load(std::memory_order_relaxed); if (count > 0) { if (count_.compare_exchange_strong(count, count-1, std::memory_order_acq_rel, std::memory_order_relaxed)) { return true; } } return false; } private: std::atomic_int count_{0}; };