С++ 0x не имеет семафоров? Как синхронизировать потоки?
Правда ли, что С++ 0x поступит без семафоров? Уже есть некоторые вопросы о переполнении стека относительно использования семафоров. Я использую их (posix semaphores) все время, чтобы поток ожидал некоторого события в другом потоке:
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 сначала блокирует мьютексы (учитывая, что тот же поток должен блокировать и разблокировать мьютекс, вы также не можете заблокировать событие1 до начала thread0 и thread1).
Итак, поскольку boost также не имеет семафоров, что является самым простым способом достижения вышеуказанного?
Ответы
Ответ 1
Вы можете легко построить один из мьютекса и переменной условия:
#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::unique_lock<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::unique_lock<decltype(mutex_)> lock(mutex_);
if(count_) {
--count_;
return true;
}
return false;
}
};
Ответ 2
На основе ответа "Максим Егорушкин" я попытался сделать пример в стиле С++ 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;
};
Ответ 3
Я решил написать наиболее надежный/общий семафор С++ 11, который я мог бы, в стиле стандарта, насколько это было возможно (обратите внимание using semaphore = ...
, вы обычно просто используете имя semaphore
, подобное обычному используя string
not 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();
}
Ответ 4
в соответствии с семафорами поз, я бы добавил
class semaphore
{
...
bool trywait()
{
boost::mutex::scoped_lock lock(mutex_);
if(count_)
{
--count_;
return true;
}
else
{
return false;
}
}
};
И я предпочитаю использовать механизм синхронизации на удобном уровне абстракции, а не всегда копировать вставку сшитой версии с использованием более основных операторов.
Ответ 5
Вы также можете проверить cpp11-on-multicore - он имеет переносимую и оптимальную реализацию семафора.
Репозиторий также содержит другие фитинги, которые дополняют потоки С++ 11.
Ответ 6
Вы можете работать с переменными mutex и condition. Вы получаете эксклюзивный доступ с мьютексом, проверяете, хотите ли вы продолжить или нужно ждать другого конца. Если вам нужно подождать, вы ждете в состоянии. Когда другой поток определяет, что вы можете продолжить, он сигнализирует о состоянии.
В библиотеке boost:: thread есть короткий , который вы, скорее всего, просто скопируете (С++ 0x и boost thread libs очень похожи).
Ответ 7
Также может быть полезна оболочка семафора 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();
Ответ 8
Я нашел shared_ptr и weak_ptr, длинный со списком, выполнил требуемую работу. Моя проблема заключалась в том, что у меня было несколько клиентов, которые хотели бы взаимодействовать с внутренними данными хоста. Как правило, хост обновляет данные самостоятельно, однако, если клиент запрашивает его, хост должен прекратить обновление до тех пор, пока клиенты не получат доступ к данным хоста. В то же время клиент может запрашивать эксклюзивный доступ, чтобы никакие другие клиенты или хост не могли изменять данные хоста.
Как я это сделал, я создал структуру:
struct UpdateLock
{
typedef std::shared_ptr< UpdateLock > ptr;
};
У каждого клиента будет такой член:
UpdateLock::ptr m_myLock;
Тогда у хоста будет элемент weak_ptr для исключительности и список слабых_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.empty()).
Очередь может быть добавлена для уменьшения между эксклюзивными и неэксклюзивными блокировками, однако до сих пор у меня не было коллизий, поэтому я намерен подождать, пока это не произойдет, чтобы добавить решение (в основном, у меня есть реальный тест состояние).
Пока это хорошо работает для моих нужд; Я могу представить себе необходимость расширения этого, и некоторые проблемы, которые могут возникнуть в результате расширенного использования, однако, это было быстро реализовано и требовало очень небольшого пользовательского кода.
Ответ 9
Если кто-то заинтересован в атомной версии, вот реализация. Ожидается, что производительность будет лучше, чем версия мьютекса и условной переменной.
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};
};