Producentkonsument med trådar och använder boost ringbuffert

Jag har två trådar, den ena är producenten och den andra är konsumenten. Min konsument är alltid sen (på grund av ett dyrt funktionsanrop, simulerat i nedanstående kod med sovplatser) så jag har använt ringbuffert eftersom jag har råd att förlora några händelser.

Jag letar efter om min låsning ser bra ut och allmänna kommentarer om c ++ granskning.

#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 ett cirkulärt buffertkontext är det vanliga sättet att undvika upptagen väntan att ha två semaforer. Först för att blockera en producent när en buffert är full och för det andra att blockera en konsument när bufferten är tom. När en process passerar sin semafor och gör sitt jobb ska den signalera peer.


Den cirkulära bufferten är bra när konsumenten bara ibland är sen och du kan inte råd att förlora data. I din situation ser det ut som en felaktig lösning: producenten blir nedsatt av konsumtionshastigheten, och konsumenten presenteras med de inaktuella uppgifterna.

Ett typiskt svar är att låta producenten springa i full fart. , och trippelbuffra produktionen (åtminstone garanterar det att konsumenten skulle få de senast producerade uppgifterna). Förlåt den skamlösa självreklam .

Kommentarer

  • tack för recensionen kommentar och länken. Men i mitt fall blockerar min konsument bara lite tid. Jag tror att det fortfarande är vettigt att använda semaforer istället för min nuvarande lösning med villkorsvariabel + lås.
  • @nomanpouigt " någon gång " skiljer sig ganska från " alltid sent ".
  • förlåt, jag menar ringbufferten storlek väljs så att den kan anpassa fördröjningen. Undrar i c ++ att det inte finns några semaforer och det implementeras med hjälp av mutexes och villkorsvariabler så om det är vettigt att använda semaforer.
  • Kan du också utarbeta din lösning av tripplebuffert och hur skulle det gälla i mitt fall ? Menar du att ' inte gör någon låsning på producentsidan och låter den köras och producentbufferten måste trippelbuffras så att konsumenten kan få den senaste informationen vid överskridanden?

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *