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