This is in continuation to this really nice one: https://stackoverflow.com/a/33500554/22598822. That post was for the sequence (t1 & t2) --> t3. Let's say I have a program running, and there are three threads that I want to keep in an inner execution sequence t1 --> t2 --> t3 (other threads may or may not be running simultaneously on the system). How can it be done?
I took the code from the reference above and tried to rework it for this goal, though unsuccessfully. My implementation is incorrect, I'm putting it here for a minimal reproducible example and maybe a possible starting point.
Header.h:
#include<thread>
#include<mutex>
#include<iostream>
#include <condition_variable>
MultiClass.h
#include "Header.h"
#include "SynchObj.h"
class MultiClass {
public:
void Run() {
std::thread t1(&MultiClass::Calc1, this);
std::thread t2(&MultiClass::Calc2, this);
std::thread t3(&MultiClass::Calc3, this);
t1.join();
t2.join();
t3.join();
}
private:
SyncObj obj;
void Calc1() {
for (int i = 0; i < 10; ++i) {
obj.waitForCompletionOfT3();
std::cout << "T1:" << i << std::endl;
obj.signalCompletionOfT1();
}
}
void Calc2() {
for (int i = 0; i < 10; ++i) {
obj.waitForCompletionOfT1();
std::cout << "T2:" << i << std::endl;
obj.signalCompletionOfT2();
}
}
void Calc3() {
for (int i = 0; i < 10; ++i) {
obj.waitForCompletionOfT2();
std::cout << "T3:" << i << std::endl;
obj.signalCompletionOfT3();
}
}
};
SynchObj.h
#include "Header.h"
class SyncObj {
std::mutex mux;
std::condition_variable cv;
bool completed[3]{ false, false, false };
public:
/***** Original (t1 & t2) --> t3 *****/
/*
void signalCompetionT1T2(int id) {
std::lock_guard<std::mutex> ul(mux);
completed[id] = true;
cv.notify_all();
}
void signalCompetionT3() {
std::lock_guard<std::mutex> ul(mux);
completed[0] = false;
completed[1] = false;
cv.notify_all();
}
void waitForCompetionT1T2() {
std::unique_lock<std::mutex> ul(mux);
cv.wait(ul, [&]() {return completed[0] && completed[1]; });
}
void waitForCompetionT3(int id) {
std::unique_lock<std::mutex> ul(mux);
cv.wait(ul, [&]() {return !completed[id]; });
}
*/
/***********************************/
/*** Unsuccessful attempt at t1 --> t2 --> t3 ***/
void signalCompletionOfT1() {
std::lock_guard<std::mutex> ul(mux);
completed[0] = true;
cv.notify_all();
}
void signalCompletionOfT2() {
std::lock_guard<std::mutex> ul(mux);
completed[0] = false;
completed[1] = true;
cv.notify_all();
}
void signalCompletionOfT3() {
std::lock_guard<std::mutex> ul(mux);
completed[0] = false;
completed[1] = false;
completed[2] = true;
cv.notify_all();
}
void waitForCompletionOfT1() {
std::unique_lock<std::mutex> ul(mux);
cv.wait(ul, [&]() {return !completed[2]; });
}
void waitForCompletionOfT2() {
std::unique_lock<std::mutex> ul(mux);
cv.wait(ul, [&]() {return !completed[0]; });
}
void waitForCompletionOfT3() {
std::unique_lock<std::mutex> ul(mux);
cv.wait(ul, [&]() {return !completed[1]; });
}
};
Source.cpp:
#include "Header.h"
#include "MultiClass.h"
int main() {
MultiClass m;
m.Run();
return 0;
}
Possible output #1 (Desired):
T1:1
T1:2
T1:3
T1:4
T1:5
T1:6
T1:7
T1:8
T1:9
T2:0
T2:1
T2:2
T2:3
T2:4
T2:5
T2:6
T2:7
T2:8
T2:9
T3:0
T3:1
T3:2
T3:3
T3:4
T3:5
T3:6
T3:7
T3:8
T3:9
Possible output #2:
0
T2:1
T2:2
T2:3
T2:4
T2:5
T2:6
T2:7
T2:8
T2:9
Using Semaphores would a bit more straight forward for this application. The following program outputs your possible output #1:
Compiled with g++ 12.2.1 with command line: g++ -std=c++20 -pthread MultiClass.cpp
Edit: As others have pointed out, doing this single threaded would be better.
MultiClass.cpp
#include<thread>
#include<mutex>
#include<iostream>
#include<semaphore>
class MultiClass {
public:
void Run() {
std::thread t1(&MultiClass::Calc1, this);
std::thread t2(&MultiClass::Calc2, this);
std::thread t3(&MultiClass::Calc3, this);
t1.join();
t2.join();
t3.join();
}
private:
std::binary_semaphore t1{1};
std::binary_semaphore t2{0};
std::binary_semaphore t3{0};
void Calc1() {
t1.acquire();
for (int i = 0; i < 10; ++i) {
std::cout << "T1:" << i << std::endl;
}
t2.release();
}
void Calc2() {
t2.acquire();
for (int i = 0; i < 10; ++i) {
std::cout << "T2:" << i << std::endl;
}
t3.release();
}
void Calc3() {
t3.acquire();
for (int i = 0; i < 10; ++i) {
std::cout << "T3:" << i << std::endl;
}
}
};
int main() {
MultiClass m;
m.Run();
return 0;
}