C ++ 0x non ha semafori? Come sincronizzare i thread?

È vero che C ++ 0x verrà senza semafori? Ci sono già alcune domande su Stack Overflow sull’uso dei semafori. Li uso (semafori di posix) sempre per lasciare che un thread attenda qualche evento in un altro thread:

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

Se lo farei con un mutex:

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

Problema: è brutto e non è garantito che thread1 blocchi prima il mutex (Dato che lo stesso thread dovrebbe bloccare e sbloccare un mutex, non è ansible bloccare event1 prima che thread0 e thread1 inizino).

Quindi, poiché boost non ha neanche i semafori, qual è il modo più semplice per ottenere quanto sopra?

Puoi facilmente crearne uno da un mutex e una variabile di condizione:

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

Basato sulla risposta di “Maxim Yegorushkin”, ho cercato di fare l’esempio in stile 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; }; 

Ho deciso di scrivere il semaforo C ++ 11 più robusto / generico che potessi, nello stile dello standard il più ansible (nota using semaphore = ... , normalmente si userebbe solo il nome semaphore simile a quello usato normalmente per la string non 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(); } 

in accordo con i semafori di posix, aggiungerei

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

E preferisco di gran lunga utilizzare un meccanismo di sincronizzazione a un livello di astrazione conveniente, piuttosto che copiare sempre una versione unita insieme usando più operatori di base.

Puoi anche controllare cpp11-on-multicore – ha un’implementazione semaforo portatile e ottimale.

Il repository contiene anche altri gadget di threading che completano il threading di c ++ 11.

Puoi lavorare con le variabili mutex e condition. Ottieni l’accesso esclusivo con il mutex, controlla se vuoi continuare o devi aspettare l’altro capo. Se hai bisogno di aspettare, aspetti in una condizione. Quando l’altro thread determina che è ansible continuare, segnala la condizione.

C’è un breve esempio nella libreria boost :: thread che probabilmente puoi semplicemente copiare (le librerie C ++ 0x e boost sono molto simili).

Può anche essere utile il wrapper del semaforo RAII nei thread:

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

Esempio di utilizzo nell’app 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(); 

Ho trovato il shared_ptr e weak_ptr, un lungo con una lista, ha fatto il lavoro che mi serviva. Il mio problema era che avevo diversi clienti che volevano interagire con i dati interni di un host. In genere, l’host aggiorna i dati su di esso, tuttavia, se un client lo richiede, l’host deve interrompere l’aggiornamento fino a quando nessun client accede ai dati host. Allo stesso tempo, un client potrebbe richiedere l’accesso esclusivo, in modo che nessun altro client, né l’host, possa modificare i dati dell’host.

Come ho fatto questo, ho creato una struttura:

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

Ogni cliente avrebbe un membro di tale:

 UpdateLock::ptr m_myLock; 

Quindi l’host avrebbe un membro weak_ptr per l’esclusività e un elenco di weak_ptrs per i blocchi non esclusivi:

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

C’è una funzione per abilitare il blocco e un’altra funzione per verificare se l’host è bloccato:

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

Eseguo il test dei blocchi in LockUpdate, IsUpdateLocked e periodicamente nella routine di aggiornamento dell’host. Provare per un lock è semplice come controllare se il weak_ptr è scaduto, e rimuovere qualsiasi scaduto dall’elenco m_locks (lo faccio solo durante l’aggiornamento dell’host), posso controllare se l’elenco è vuoto; allo stesso tempo, ottengo lo sblocco automatico quando un client reimposta il parametro shared_ptr a cui sono appesi, cosa che accade anche quando un client viene distrutto automaticamente.

L’effetto generale è che, poiché i client raramente hanno bisogno dell’esclusività (tipicamente riservata solo alle aggiunte e alle eliminazioni), la maggior parte delle volte una richiesta di LockUpdate (false), vale a dire non esclusiva, ha esito positivo (! M_exclusiveLock). E LockUpdate (true), una richiesta di esclusività, ha esito positivo solo quando entrambi (! M_exclusiveLock) e (m_locks.empty ()).

Potrebbe essere aggiunta una coda per mitigare tra lock esclusivi e non esclusivi, tuttavia non ho avuto collisioni fino ad ora, quindi intendo aspettare che ciò accada per aggiungere la soluzione (soprattutto perché ho una condizione di test del mondo reale).

Finora questo funziona bene per i miei bisogni; Posso immaginare la necessità di espandere questo e alcuni problemi che potrebbero sorgere dall’uso esteso, tuttavia, questo è stato rapido da implementare e ha richiesto pochissimo codice personalizzato.

Nel caso qualcuno sia interessato alla versione atomica, ecco l’implementazione. La prestazione è prevista migliore rispetto alla versione mutex e condizione variabile.

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