Jai deux threads, lun est le producteur et lautre est le consommateur. Mon consommateur est toujours en retard (en raison dun appel de fonction coûteux, simulé dans le code ci-dessous à laide de sleeps), jai donc utilisé un tampon en anneau car je peux me permettre de perdre certains événements.
Je cherche à voir si mon verrouillage semble correct et les commentaires généraux de révision C ++.
#include <iostream> #include <thread> #include <chrono> #include <vector> #include <atomic> #include <boost/circular_buffer.hpp> #include <condition_variable> #include <functional> std::atomic<bool> mRunning; std::mutex m_mutex; std::condition_variable m_condVar; class VecBuf { private: std::vector<int8_t> vec; public: VecBuf() = default; VecBuf(std::vector<int8_t> v) { vec = v; } }; std::vector<int8_t> data{ 10, 20, 30 }; class Detacher { public: template<typename Function, typename ... Args> void createTask(Function &&func, Args&& ... args) { m_threads.emplace_back(std::forward<Function>(func), std::forward<Args>(args)...); } Detacher() = default; Detacher(const Detacher&) = delete; Detacher & operator=(const Detacher&) = delete; Detacher(Detacher&&) = default; Detacher& operator=(Detacher&&) = default; ~Detacher() { for (auto& thread : m_threads) { thread.join(); } } private: std::vector<std::thread> m_threads; }; void foo_1(boost::circular_buffer<VecBuf> *cb) { while (mRunning) { std::unique_lock<std::mutex> mlock(m_mutex); m_condVar.wait(mlock, [=]() { return !cb->empty(); }); VecBuf local_data(cb->front()); cb->pop_front(); mlock.unlock(); if (!mRunning) { break; } //simulate time consuming function call and consume local_data here std::this_thread::sleep_for(std::chrono::milliseconds(16)); } while (cb->size()) { VecBuf local_data(cb->front()); cb->pop_front(); if (!mRunning) { break; } } } void foo_2(boost::circular_buffer<VecBuf> *cb) { while (mRunning) { std::unique_lock<std::mutex> mlock(m_mutex); while (cb->full()) { mlock.unlock(); /* can we do better than this? */ std::this_thread::sleep_for(std::chrono::milliseconds(100)); mlock.lock(); } cb->push_back(VecBuf(data)); m_condVar.notify_one(); } } int main() { mRunning = true; boost::circular_buffer<VecBuf> cb(100); Detacher thread_1; thread_1.createTask(foo_1, &cb); Detacher thread_2; thread_2.createTask(foo_2, &cb); std::this_thread::sleep_for(std::chrono::milliseconds(20000)); mRunning = false; }
Réponse
/* can we do better than this? */
Dans un contexte de tampon circulaire, la manière standard déviter une attente occupée est davoir deux sémaphores. Premièrement pour bloquer un producteur lorsquun tampon est plein, et deuxièmement pour bloquer un consommateur lorsque le tampon est vide. Une fois quun processus a passé son sémaphore et fait son travail, il doit signaler le pair.
Le tampon circulaire est bon lorsque le consommateur est seulement parfois en retard et vous ne pouvez pas permettre de perdre des données. Dans votre situation, cela ressemble à une mauvaise solution: le producteur devient étranglé par le taux de consommation, et le consommateur est présenté avec les données périmées.
Une réponse typique est de laisser le producteur fonctionner à plein régime , et triple tampon de la production (au moins, cela garantit que le consommateur obtiendra les données les plus récemment produites). Veuillez pardonner l auto-promotion sans vergogne .
Commentaires
- merci pour cet avis commentaire et le lien. Cependant, dans mon cas, mon consommateur ne bloque que quelques temps. Je pense quil est toujours logique dutiliser des sémaphores au lieu de ma solution actuelle en utilisant la variable de condition + verrou.
- @nomanpouigt " quelque temps " est assez différent de " toujours en retard ".
- désolé, je veux dire le tampon en anneau la taille est choisie de manière à pouvoir supporter le retard. Vous vous demandez en C ++ quil ny a pas de sémaphores et quil est implémenté à laide de mutex et de variable de condition, donc sil est logique dutiliser des sémaphores.
- Pouvez-vous également élaborer votre solution de tampon tripple et comment cela sappliquerait-il dans mon cas ? Voulez-vous dire ne ' t faire aucun verrouillage côté producteur et laissez-le fonctionner et le tampon producteur doit être triplement tamponné afin que le consommateur puisse obtenir des données récentes en cas de dépassement?