Jeg har to tråder, den ene er produsenten og den andre er forbruker. Forbrukeren min er alltid forsinket (på grunn av et kostbart funksjonsanrop, simulert under koden ved hjelp av hvilemodus), så jeg har brukt ringbuffer da jeg har råd til å miste noen hendelser.
Jeg ser etter om låsingen min ser bra ut og generelle kommentarer om c ++ gjennomgang.
#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; }
Svar
/* can we do better than this? */
I en sirkulær bufferkontekst er standard måten å unngå travelt å vente på å ha to semaforer. Først for å blokkere en produsent når bufferen er full, og den andre for å blokkere en forbruker når bufferen er tom. Når en prosess har passert semaforen, og gjør jobben sin, bør den signalisere jevnaldrende.
Den sirkulære bufferen er god når forbrukeren bare noen ganger er sen og du kan ikke råd til å miste data. I din situasjon ser det ut som en feil løsning: produsenten blir strupet av forbrukshastigheten, og forbrukeren blir presentert med de foreldede dataene.
Et typisk svar er å la produsenten løpe i full fart. , og trippelbuffer produksjonen (i det minste garanterer det at forbrukeren ville få de sist produserte dataene). Tilgi den skamløse selvkampanjen .
Kommentarer
- takk for anmeldelsen kommentar og lenken. Imidlertid blokkerer forbrukeren min bare litt tid. Jeg tror det fortsatt er fornuftig å bruke semaforer i stedet for den nåværende løsningen min ved å bruke tilstandsvariabelen + lås.
- @nomanpouigt " en stund " er ganske forskjellig fra " alltid sent ".
- beklager, jeg mener ringbufferen størrelse er valgt slik at den er i stand til å tilpasse forsinkelsen. Lurer på i c ++ er det ingen semaforer, og den er implementert ved hjelp av mutexes og tilstandsvariabler, så hvis det er fornuftig å bruke semaforer.
- Kan du også utdype løsningen din med tripple buffer og hvordan vil den gjelde i mitt tilfelle ? Mener du ikke at ' ikke gjør noen låsing på produsentens side og lar den kjøre, og produsentbuffer må trippelbufres slik at forbrukeren kan få nylige data i tilfelle overskridelser?