C ++ 0x не имеет семафоров?Как синхронизировать потоки? - PullRequest
117 голосов
/ 25 января 2011

Правда ли, что 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 ]

155 голосов
/ 25 января 2011

Вы можете легко создать его из мьютекса и условной переменной:

#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;
    }
};
98 голосов
/ 29 октября 2013

Основываясь на ответе Максима Егорушкина , я попытался сделать пример в стиле 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;
};
36 голосов
/ 09 января 2015

Я решил написать самый надежный / универсальный семафор 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();
}
14 голосов
/ 05 ноября 2012

в соответствии с семафорами posix, я бы добавил

class semaphore
{
    ...
    bool trywait()
    {
        boost::mutex::scoped_lock lock(mutex_);
        if(count_)
        {
            --count_;
            return true;
        }
        else
        {
            return false;
        }
    }
};

И я очень предпочитаю использовать механизм синхронизации на удобном уровне абстракции, а не копировать склеенные версии, используя более простые операторы.

8 голосов
/ 31 марта 2015

Вы также можете проверить cpp11-on-multicore - у него есть портативная и оптимальная реализация семафора.

Хранилище также содержит другие полезные функции потоков, которые дополняют потоки c ++ 11.

6 голосов
/ 25 января 2011

Вы можете работать с мьютексом и условными переменными. Вы получаете эксклюзивный доступ с мьютексом, проверьте, хотите ли вы продолжить или нужно ждать другого конца. Если вам нужно ждать, вы ждете в состоянии. Когда другой поток определяет, что вы можете продолжить, он сигнализирует об этом условии.

В библиотеке boost :: thread есть короткий пример , который вы, скорее всего, можете просто скопировать (библиотеки C ++ 0x и Boost очень похожи).

2 голосов
/ 29 октября 2014

Также может быть полезна обёртка 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();
1 голос
/ 20 марта 2017

Я нашел shared_ptr и weak_ptr, длинный со списком, выполнил работу, в которой я нуждался.Моя проблема заключалась в том, что у меня было несколько клиентов, желающих взаимодействовать с внутренними данными хоста.Обычно хост обновляет данные самостоятельно, однако, если клиент запрашивает его, хост должен прекратить обновление, пока клиенты не получат доступ к данным хоста.В то же время клиент может запросить монопольный доступ, чтобы ни другие клиенты, ни хост не могли изменить данные этого хоста.

Как я это сделал, я создал структуру:

struct UpdateLock
{
    typedef std::shared_ptr< UpdateLock > ptr;
};

У каждого клиента будет такой член:

UpdateLock::ptr m_myLock;

Тогда у хоста будет элемент слабой_птр для исключительности и список слабых_птр для неисключительных блокировок:

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 ()).

Очередь может быть добавлена ​​для смягчения между исключительными и неисключительными блокировками,однако до сих пор у меня не было коллизий, поэтому я собираюсь подождать, пока это произойдет, чтобы добавить решение (в основном, так что у меня есть условия тестирования в реальном мире).

Пока это хорошо работает для моих нужд;Я могу себе представить необходимость его расширения и некоторые проблемы, которые могут возникнуть из-за расширенного использования, однако, это было быстро реализовано и требовало очень мало пользовательского кода.

0 голосов
/ 13 февраля 2017

В случае, если кто-то заинтересован в атомарной версии, вот реализация. Ожидается, что производительность будет лучше, чем версия переменной mutex & condition.

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};
};
...