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 103

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