C ++ 0x não tem semáforos? Como sincronizar threads?

É verdade que o C ++ 0x virá sem semáforos? Já existem algumas questões sobre o Stack Overflow em relação ao uso de semáforos. Eu os uso (semáforos posix) o tempo todo para deixar um thread esperar por algum evento em outro thread:

void thread0(...) { doSomething0(); event1.wait(); ... } void thread1(...) { doSomething1(); event1.post(); ... } 

Se eu fizesse isso com um mutex:

 void thread0(...) { doSomething0(); event1.lock(); event1.unlock(); ... } void thread1(...) { event1.lock(); doSomethingth1(); event1.unlock(); ... } 

Problema: É feio e não é garantido que o thread1 bloqueia o mutex primeiro (dado que o mesmo thread deve bloquear e desbloquear um mutex, você também não pode bloquear o event1 antes de thread0 e thread1 serem iniciados).

Então, como o boost também não possui semáforos, qual é a maneira mais simples de alcançar o que foi dito acima?

Você pode facilmente construir um a partir de um mutex e uma variável de condição:

 #include  #include  class semaphore { private: std::mutex mutex_; std::condition_variable condition_; unsigned long count_ = 0; // Initialized as locked. public: void notify() { std::unique_lock lock(mutex_); ++count_; condition_.notify_one(); } void wait() { std::unique_lock lock(mutex_); while(!count_) // Handle spurious wake-ups. condition_.wait(lock); --count_; } bool try_wait() { std::unique_lock lock(mutex_); if(count_) { --count_; return true; } return false; } }; 

Com base na resposta de “Maxim Yegorushkin”, tentei fazer o exemplo no estilo C ++ 11.

 #include  #include  class Semaphore { public: Semaphore (int count_ = 0) : count(count_) {} inline void notify() { std::unique_lock lock(mtx); count++; cv.notify_one(); } inline void wait() { std::unique_lock lock(mtx); while(count == 0){ cv.wait(lock); } count--; } private: std::mutex mtx; std::condition_variable cv; int count; }; 

Eu decidi escrever o mais robusto / genérico C ++ 11 semáforo que eu poderia, no estilo do padrão, tanto quanto eu poderia (note using semaphore = ... , você normalmente só usaria o nome do semaphore semelhante ao normalmente usando string não basic_string ):

 template  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 bool wait_for(const std::chrono::duration& d); template bool wait_until(const std::chrono::time_point& t); native_handle_type native_handle(); private: Mutex mMutex; CondVar mCv; size_t mCount; }; using semaphore = basic_semaphore; template  basic_semaphore::basic_semaphore(size_t count) : mCount{count} {} template  void basic_semaphore::notify() { std::lock_guard lock{mMutex}; ++mCount; mCv.notify_one(); } template  void basic_semaphore::wait() { std::unique_lock lock{mMutex}; mCv.wait(lock, [&]{ return mCount > 0; }); --mCount; } template  bool basic_semaphore::try_wait() { std::lock_guard lock{mMutex}; if (mCount > 0) { --mCount; return true; } return false; } template  template bool basic_semaphore::wait_for(const std::chrono::duration& d) { std::unique_lock lock{mMutex}; auto finished = mCv.wait_for(lock, d, [&]{ return mCount > 0; }); if (finished) --mCount; return finished; } template  template bool basic_semaphore::wait_until(const std::chrono::time_point& t) { std::unique_lock lock{mMutex}; auto finished = mCv.wait_until(lock, t, [&]{ return mCount > 0; }); if (finished) --mCount; return finished; } template  typename basic_semaphore::native_handle_type basic_semaphore::native_handle() { return mCv.native_handle(); } 

de acordo com semáforos posix, gostaria de acrescentar

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

E prefiro muito mais usar um mecanismo de synchronization em um nível conveniente de abstração, em vez de sempre copiar e colar uma versão combinada usando operadores mais básicos.

Você também pode verificar o cpp11-on-multicore – ele tem uma implementação de semáforo portátil e ideal.

O repository também contém outras funcionalidades de encadeamento que complementam o encadeamento c ++ 11.

Você pode trabalhar com mutex e variables ​​de condição. Você ganha access exclusivo com o mutex, verifique se deseja continuar ou precisa esperar pelo outro lado. Se você precisa esperar, você espera em uma condição. Quando o outro segmento determina que você pode continuar, ele sinaliza a condição.

Há um pequeno exemplo na biblioteca boost :: thread que você provavelmente copia (as bibliotecas de thread C ++ 0x e boost são muito similares).

Também pode ser útil wrapper de semáforo RAII em threads:

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

Exemplo de uso no aplicativo multithread:

 boost::ptr_vector threads; Semaphore semaphore; for (...) { ... auto t = new std::thread([..., &semaphore] { ScopedSemaphore scopedSemaphore(semaphore); ... } ); threads.push_back(t); } for (auto& t : threads) t.join(); 

Eu encontrei o shared_ptr e weak_ptr, um longo com uma lista, fiz o trabalho que eu precisava. Meu problema era que eu tinha vários clientes querendo interagir com os dados internos de um host. Normalmente, o host atualiza os dados por conta própria, no entanto, se um cliente solicitar, o host precisará parar a atualização até que nenhum cliente esteja acessando os dados do host. Ao mesmo tempo, um cliente poderia solicitar access exclusivo, para que nenhum outro cliente, nem o host, pudesse modificar esses dados do host.

Como eu fiz isso foi, criei uma estrutura:

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

Cada cliente teria um membro de tal:

 UpdateLock::ptr m_myLock; 

Em seguida, o host teria um membro weak_ptr para exclusividade e uma lista de weak_ptrs para bloqueios não exclusivos:

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

Existe uma function para ativar o bloqueio e outra function para verificar se o host está bloqueado:

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

Eu testo para bloqueios em LockUpdate, IsUpdateLocked e periodicamente na rotina de atualização do host. O teste de um bloqueio é tão simples quanto verificar se o weak_ptr expirou e remover qualquer expirado da lista m_locks (só faço isso durante a atualização do host), posso verificar se a lista está vazia; ao mesmo tempo, recebo desbloqueio automático quando um cliente redefine o shared_ptr no qual estão pendurados, o que também acontece quando um cliente é destruído automaticamente.

O efeito geral é que, como os clientes raramente precisam de exclusividade (geralmente reservada apenas para adições e exclusões), na maioria das vezes, uma solicitação para LockUpdate (false), ou seja, não exclusiva, é bem-sucedida (! M_exclusiveLock). E um LockUpdate (true), uma solicitação de exclusividade, só é bem-sucedido quando ambos (! M_exclusiveLock) e (m_locks.empty ()).

Uma fila poderia ser adicionada para mitigar bloqueios exclusivos e não exclusivos, no entanto, eu não tive colisões até agora, então pretendo esperar até que isso aconteça para adicionar a solução (principalmente para que eu tenha uma condição de teste do mundo real).

Até agora isso está funcionando bem para minhas necessidades; Eu posso imaginar a necessidade de expandir isso, e alguns problemas que podem surgir sobre o uso expandido, no entanto, isso foi rápido para implementar e exigiu muito pouco código personalizado.

Caso alguém esteja interessado na versão atômica, aqui está a implementação. Espera-se que o desempenho seja melhor que o mutex e a versão da variável de condição.

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