Ich habe zwei Threads, einer ist der Produzent und der andere ist Consumer. Mein Verbraucher ist immer zu spät (aufgrund eines kostspieligen Funktionsaufrufs, der im folgenden Code mit Schlaf simuliert wird), daher habe ich Ringpuffer verwendet, da ich es mir leisten kann, einige Ereignisse zu verlieren.
Ich freue mich darauf, ob meine Sperre in Ordnung ist und allgemeine C ++ – Überprüfungskommentare.
#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; }
Antwort
/* can we do better than this? */
In einem zirkulären Pufferkontext besteht die Standardmethode zum Vermeiden von Wartezeiten darin, zwei Semaphoren zu haben. Erstens, um einen Produzenten zu blockieren, wenn ein Puffer voll ist, und zweitens, um einen Verbraucher zu blockieren, wenn der Puffer leer ist. Sobald ein Prozess sein Semaphor passiert und seine Arbeit erledigt, sollte er dem Peer ein Signal geben.
Der Umlaufpuffer ist gut, wenn der Verbraucher nur manchmal zu spät kommt und nicht Daten verlieren. In Ihrer Situation sieht es nach einer falschen Lösung aus: Der Produzent wird durch die Verbrauchsrate gedrosselt, und dem Verbraucher werden die veralteten Daten angezeigt.
Eine typische Antwort besteht darin, den Produzenten mit voller Geschwindigkeit laufen zu lassen und die Produktion dreifach puffern (zumindest garantiert dies, dass der Verbraucher die zuletzt produzierten Daten erhält). Bitte verzeihen Sie die schamlose Eigenwerbung .
Kommentare
- Vielen Dank für die Bewertung Kommentar und der Link. In meinem Fall blockiert mein Verbraucher jedoch nur einige Zeit. Ich denke, es ist immer noch sinnvoll, Semaphore anstelle meiner aktuellen Lösung mit Bedingungsvariable + Sperre zu verwenden.
- @nomanpouigt " einige Zeit " unterscheidet sich erheblich von " immer spät ".
- Entschuldigung, ich meine den Ringpuffer Die Größe wird so gewählt, dass die Verzögerung angepasst werden kann. In C ++ gibt es keine Semaphore und es wird unter Verwendung von Mutexen und Bedingungsvariablen implementiert. Wenn es also sinnvoll ist, Semaphoren zu verwenden.
- Können Sie bitte auch Ihre Lösung des Tripple-Puffers erläutern und wie würde sie in meinem Fall angewendet werden? ? Meinen Sie damit, dass ' keine Sperren auf der Herstellerseite durchführt und laufen lässt und der Erzeugerpuffer dreifach gepuffert werden muss, damit der Verbraucher im Falle von Überläufen aktuelle Daten erhalten kann?