C++0x no tiene semáforos? Cómo sincronizar hilos?


Es cierto que C++0x vendrá sin semáforos? Ya hay algunas preguntas sobre Stack Overflow con respecto al uso de semáforos. Los uso (semáforos posix) todo el tiempo para dejar que un hilo espere algún evento en otro hilo:

void thread0(...)
{
  doSomething0();

  event1.wait();

  ...
}

void thread1(...)
{
  doSomething1();

  event1.post();

  ...
}

Si hiciera eso con un mutex:

void thread0(...)
{
  doSomething0();

  event1.lock(); event1.unlock();

  ...
}

void thread1(...)
{
  event1.lock();

  doSomethingth1();

  event1.unlock();

  ...
}

Problema: Es feo y no está garantizado que thread1 bloquee el mutex primero (Dado que el mismo hilo debe bloquear y desbloquear un mutex, tampoco puede bloquear event1 antes de thread0 y thread1 iniciado).

Así que dado que boost tampoco tiene semáforos, ¿cuál es la forma más sencilla de lograr lo anterior?

Author: Whymarrh, 2011-01-25

9 answers

Puede construir fácilmente uno a partir de un mutex y una variable de condición:

#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;
    }
};
 131
Author: Maxim Egorushkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-13 15:00:26

Basado en la respuesta de "Maxim Yegorushkin", traté de hacer el ejemplo en estilo 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;
};
 94
Author: Tsuneo Yoshioka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-11 02:06:35

Decidí escribir el semáforo C++11 más robusto/genérico que pudiera, al estilo del estándar tanto como pudiera (nota using semaphore = ..., normalmente solo usarías el nombre semaphore similar a usar normalmente string no 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();
}
 33
Author: David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-11 16:48:38

En concordancia con los semáforos posix, yo añadiría

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

Y prefiero mucho usar un mecanismo de sincronización en un nivel conveniente de abstracción, en lugar de copiar siempre pegando una versión unida utilizando operadores más básicos.

 13
Author: Michael Zillich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-05 17:24:49

También puede ver cpp11-on-multicore - tiene una implementación de semáforo portátil y óptima.

El repositorio también contiene otras ventajas de threading que complementan el threading de c++11.

 7
Author: onqtam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-31 17:19:04

Puede trabajar con variables mutex y condition. Obtiene acceso exclusivo con el mutex, verifique si desea continuar o necesita esperar al otro extremo. Si usted necesita esperar, usted espera en una condición. Cuando el otro hilo determina que puede continuar, señala la condición.

Hay un breve ejemplo en la biblioteca boost::thread que probablemente puedas copiar (las bibliotecas de subprocesos de C++0x y boost son muy similares).

 5
Author: David Rodríguez - dribeas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 10:41:48

También puede ser útil RAII semaphore wrapper en hilos:

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

Ejemplo de uso en la aplicación multithread:

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();
 0
Author: slasla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-06 11:40:50

Encontré el shared_ptr y weak_ptr, un largo con una lista, hizo el trabajo que necesitaba. Mi problema era que tenía varios clientes que querían interactuar con los datos internos de un host. Por lo general, el host actualiza los datos por su cuenta, sin embargo, si un cliente lo solicita, el host debe dejar de actualizar hasta que ningún cliente acceda a los datos del host. Al mismo tiempo, un cliente podría solicitar acceso exclusivo, de modo que ningún otro cliente, ni el host, podría modificar esos datos del host.

Cómo hice esto fue, yo creado una estructura:

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

Cada cliente tendría un miembro de tales:

UpdateLock::ptr m_myLock;

Entonces el host tendría un miembro weak_ptr para exclusividad, y una lista de weak_ptrs para bloqueos no exclusivos:

std::weak_ptr< UpdateLock > m_exclusiveLock;
std::list< std::weak_ptr< UpdateLock > > m_locks;

Hay una función para habilitar el bloqueo, y otra función para verificar si el host está bloqueado:

UpdateLock::ptr LockUpdate( bool exclusive );       
bool IsUpdateLocked( bool exclusive ) const;

Pruebo bloqueos en LockUpdate, IsUpdateLocked, y periódicamente en la rutina de actualización del host. Probar un bloqueo es tan simple como comprobar si el weak_ptr ha expirado, y al eliminar cualquier caducado de la lista m_locks (solo lo hago durante la actualización del host), puedo verificar si la lista está vacía; al mismo tiempo, obtengo un desbloqueo automático cuando un cliente restablece el shared_ptr al que se aferra, lo que también sucede cuando un cliente se destruye automáticamente.

El efecto over all es, ya que los clientes rara vez necesitan exclusividad (normalmente reservada solo para adiciones y eliminaciones), la mayoría de las veces una solicitud de LockUpdate (false ), es decir, no exclusiva, tiene éxito siempre y cuando (! m_exclusiveLock). Y un LockUpdate (verdadero), una solicitud de exclusividad, solo tiene éxito cuando ambos (! m_exclusiveLock) y (m_locks.vaciar()).

Se podría agregar una cola para mitigar entre bloqueos exclusivos y no exclusivos, sin embargo, no he tenido colisiones hasta ahora, así que tengo la intención de esperar hasta que eso suceda para agregar la solución (principalmente para que tenga una condición de prueba del mundo real).

Hasta ahora esto está funcionando bien para mis necesidades; puedo imaginar la necesidad de expandir esto, y algunos problemas que podrían surgir durante el uso ampliado, sin embargo, esto fue rápido de implementar, y requirió muy poco código personalizado.

 0
Author: Kit10,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-20 19:19:08

En caso de que alguien esté interesado en la versión atómica, aquí está la implementación. Se espera que el rendimiento sea mejor que la versión variable 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};
};
 -3
Author: Jeffery,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-13 04:20:04