?
Hallo,
danke für den Tipp, ich hab mich in der Zwischenzeit etwas eingelesen und herumprobiert, allerdings kann nicht nicht wirklich sagen, dass ich alles komplett verstanden habe.
Mein Code zum Testen ist dieser hier, ich hoffe das ist nicht zu viel:
#include <iostream>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <cassert>
using namespace std::chrono;
using std::cout; using std::endl;
std::mutex m_CondMutex;
std::condition_variable m_CondVar;
std::mutex m_Mutex; //Soll m_MID und m_Token schützen
int64_t m_MID = 0;
unsigned m_Token = 0;
void SetMID(const int64_t &t)
{
std::unique_lock<std::mutex> lk(m_Mutex);
m_MID = t;
if(t < 0)
m_Token++;
}
int64_t GetMID()
{
std::unique_lock<std::mutex> lk(m_Mutex);
return m_MID;
}
unsigned GetToken()
{
std::unique_lock<std::mutex> lk(m_Mutex);
return m_Token;
}
class Workers {
virtual void Work() = 0;
protected:
unsigned m_T;
int m_Nr; // Soll hier jetzt das neuste MID sein
public:
Workers(unsigned t) : m_T(t) {}
static void Run(Workers *pW, int Nr) {
cout << "Start Thread Nr." << Nr << endl;
pW->m_Nr = Nr;
pW->Work();
delete pW;
cout << "Ende Thread Nr. " << Nr << endl;
}
};
class ThreadA : public Workers //Lädt MID
{
virtual void Work()
{
std::unique_lock<std::mutex> lk(m_CondMutex);
std::this_thread::sleep_for(1s); //Arbeiten simulieren, MID aus DB laden
if(GetToken() == m_T)
{
SetMID(m_Nr);
cout << "MID auf " << m_Nr << " gesetzt" << endl;
m_CondVar.notify_all();
}
else
cout << "MID NICHT auf " << m_Nr << " gesetzt" << endl;
}
public:
ThreadA(unsigned t) : Workers(t) {}
};
class ThreadB : public Workers // Neuste MID verwenden
{
virtual void Work()
{
std::unique_lock<std::mutex> lk(m_CondMutex);
cout << "Vor-MID: " << GetMID() << endl;
std::this_thread::sleep_for(500ms); //Arbeiten simulieren
m_CondVar.wait(lk, [&](){return GetMID() != -1;});
cout << "Nach-MID: " << GetMID() << endl;
}
public:
ThreadB(unsigned t) : Workers(t) {}
};
int main(void)
{
cout << "MID ist jetzt: " << m_MID << endl;
cout << "B und A starten" << endl;
for(int i = 0; i < 5; i++)
{
//Bevor ein A startet wird MID auf -1 gesetzt und unser Token inkrementiert
SetMID(-1);
auto pA = new ThreadA(GetToken());
std::thread(Workers::Run, pA, i).detach();
}
for(int i = 4; i < 8; i++)
{
auto pB = new ThreadB(GetToken());
std::thread(Workers::Run, pB, i).detach();
}
std::this_thread::sleep_for(7s); //Einfaches warten dass die Threads fertig werden
assert(GetMID() == 4);
SetMID(GetMID()+1);
cout << "B ohne A, MID=" << GetMID() << endl;
auto t = std::thread(Workers::Run, new ThreadB(GetToken()), 8);
t.join();
return 0;
}
Kurze Erklärung zum Code:
Workers ist die gemeinsame Oberklasse aller Threads und führt die eigentliche Operation aus und gibt den Speicher wieder frei. Jeder Thread erhält hier außerdem eine aufsteigende Nr. die der Einfachkeit halber auch dem Token entspricht. ThreadA setzt MID auf seine Nr. falls der momentane Token seiner "Thread-Nr" entspricht. ThreadB soll, falls ein A existiert bzw. MID ungültig ist warten.
Von der Ausgabe her scheint es zumindest so zu funktionieren wie ich es mir gedacht habe, allerdings weiß ich nicht ob das nun einfach Zufall ist oder es tatsächlich richtig angewendet wurde.
Insbesondere m_ConMutex, welches ja in beiden Work()-Methoden gleich am Anfang erworben wird, blockt m.M.n. ThreadA, falls B zuerst mit Arbeiten anfängt und dann wird auch nur ein A nach der Reihe ausgeführt. Alle As sollte ja aber möglichst ungehindert laufen. Aber wie müsste ich das Programm dann dafür verändern?