Reputation: 106
I want to compute two things in two threads where each of them has an endless loop. Those two don't depend on each other.
Additionally I want some output on the terminal also in an endless loop (just start again each time the last output was calculated, not any step in between needed). Therefore I want some local copies of the global variables (two consecutive iterations of the first calculation and one value of the second calculation) all in a safe state.
The problem is now that I have to add some sleep to the two calculations to ever get some output from func1. I checked CPU usage and the sleep definitely lowers it. How can I work around this?
Also see the comments for testing:
#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
int n(0);
long s(1234);
double t(56.78);
std::condition_variable cond;
std::mutex m1;
std::mutex m2;
void func1() {
while (true) {
// Not needed due to wait unlocking the mutex
//std::this_thread::sleep_for(std::chrono::nanoseconds(1));
std::unique_lock<std::mutex> lck1(m1);
cond.wait(lck1);
int n1(n);
long s1(s);
cond.wait(lck1);
int n2(n);
long s2(s);
lck1.unlock();
std::unique_lock<std::mutex> lck2(m2);
double ti(t);
lck2.unlock();
// calculate and print some output
std::this_thread::sleep_for(std::chrono::milliseconds((n1*n2)/2));
std::cout << n1 << ":" << s1 << ", " << n2 << ":" << s2 << ", " << ti << std::endl;
}
}
void func2() {
while (true) {
// Why do I need this to make func1 ever proceed (ok, really seldom and random func1 got the mutex) and
// how to work around this without sleep lowering time for computations?
std::this_thread::sleep_for(std::chrono::nanoseconds(1)); // comment out to test
std::unique_lock<std::mutex> lck1(m1);
n++;
// do some stuff taking some time with s
std::this_thread::sleep_for(std::chrono::milliseconds((n*n)/3));
cond.notify_all();
}
}
void func3() {
while (true) {
// Why do I need this to make func1 ever proceed (it got the mutex never ever) and
// how to work around this without sleep lowering time for computations?
std::this_thread::sleep_for(std::chrono::nanoseconds(1)); // comment out to test
std::unique_lock<std::mutex> lck2(m2);
// do something taking some time with t
std::this_thread::sleep_for(std::chrono::seconds(2));
}
}
int main() {
std::thread t1(func1);
std::thread t2(func2);
std::thread t3(func3);
t1.join();
t2.join();
t3.join();
return 0;
}
Upvotes: 1
Views: 597
Reputation: 68611
Firstly, you must assume that condition variable wait
calls do not actually wait. A valid (though extreme) implementation is lk.unlock(); lk.lock();
, and sometimes it does behave like that due to "spurious wakeups". You really need to associate a condition with a condition variable, and loop if the condition is not met.
Secondly, if you notify the condition variable while holding the lock on the mutex, then the waiting thread will wake from the notify, only to block on the mutex, so the notifying thread may then reacquire the mutex before the waiting thread gets a chance.
It is thus better to unlock the mutex before notifying the condition variable.
lck1.unlock();
cond.notify_all();
This gives the waiting thread a chance to acquire the mutex immediately, before the notifying thread reacquires the lock.
You could add std::this_thread::yield()
calls between iterations, to give other threads a chance to acquire the lock. However, this is less than ideal --- any code where you have to explicitly do something to mess with the scheduler is a target for rewriting.
Upvotes: 1
Reputation: 249293
You can do this without a condition variable:
long s[2] = {1234,1234}; // instead of a single value
void func1() {
while (true) {
std::unique_lock<std::mutex> lck1(m1);
int n1(n);
long s1(s[0]);
long s2(s[1]);
lck1.unlock();
int n2 = n1 + 1;
// ...rest of func1 is unchanged...
void func2() {
while (true) {
std::unique_lock<std::mutex> lck1(m1);
n++;
// compute s[0]
std::this_thread::sleep_for(std::chrono::milliseconds((n*n)/3));
n++;
// compute s[1]
std::this_thread::sleep_for(std::chrono::milliseconds((n*n)/3));
std::this_thread::yield();
}
}
Upvotes: 0
Reputation: 249293
You can replace the sleep with:
std::this_thread::yield();
This will give the other thread a chance to take the lock.
http://en.cppreference.com/w/cpp/thread/yield
Upvotes: 0