Minulla on kaksi ketjua, yksi on tuottaja ja toinen kuluttaja. Kuluttajani on aina myöhässä (johtuen kalliista toimintopyynnöstä, joka on simuloitu alla olevassa koodissa lepotilojen avulla), joten olen käyttänyt rengaspuskuria, koska minulla on varaa menettää joitain tapahtumia.
Etsin, näyttääkö lukitus kunnossa ja yleisiä c ++ -arvostelu kommentteja.
#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; }
vastaus
/* can we do better than this? */
Pyöreässä puskurikontekstissa tavallinen tapa välttää kiireistä odottamista on saada kaksi semaforia. Ensin estetään tuottaja, kun puskuri on täynnä, ja toiseksi estetään kuluttaja, kun puskuri on tyhjä. Kun prosessi on läpäissyt semaforinsa ja tehnyt työnsä, sen on ilmoitettava vertaiselle.
Pyöreä puskuri on hyvä, kun kuluttaja on vain joskus myöhässä ja et voi varaa menettää tietoja. Sinun tilanteessasi se näyttää väärältä ratkaisulta: tuottaja kuristuu kulutusnopeudesta ja kuluttajalle esitetään vanhentuneet tiedot.
Tyypillinen vastaus on antaa valmistajan toimia täydellä nopeudella ja kolminkertaistaa puskurin tuotannon (ainakin se takaa, että kuluttaja saa viimeisimmät tuotetut tiedot). Anteeksi häpeämätön itsekampanja .
Kommentit
- kiitos arvostelusta kommentti ja linkki. Minun tapauksessani kuluttajani kuitenkin estää vain jonkin aikaa. Mielestäni on silti järkevää käyttää semaforeja nykyisen ratkaisuni sijaan ehdon muuttuja + lukitus avulla.
- @nomanpouigt " jonkin aikaa " on aivan erilainen kuin " aina myöhässä ".
- anteeksi, tarkoitan renkaan puskuria koko valitaan siten, että se pystyy mukauttamaan viiveen. Mietitkö, missä c ++: ssa ei ole semaforeja, ja se toteutetaan käyttämällä mutekseja ja ehtomuuttujaa, joten jos on järkevää käyttää semaforeja.
- Voitteko myös kehittää kolminkertaisen puskurin ratkaisua ja miten sitä sovellettaisiin tapauksessani ? Tarkoitatko <
älä tee mitään lukitusta tuottajapuolella ja anna sen käydä, ja tuottajapuskuri on kolminkertaisesti puskuroitava, jotta kuluttaja voi saada viimeisimmät tiedot ylitysten yhteydessä?