Thread Coordination Practical
Communication Between Threads
-
The threads run concurrently
- The data fetching thread runs continually
- The progress bar thread waits for information
- The processor thread waits until all the data has been received
-
When the download is complete
- The fetching thread terminates
- The progress bar thread terminates
- The processor thread runs
Data Sharing Between Threads
-
The downloaded data is shared by all three threads
- The data fetching appends to it
- The progress bar thread calculates its size
- The processor thread uses the data
-
Potential Data Race
- Multiple threads
- Modification
Coordination of Threads
-
We will use two bools to coordinate the threads
-
"progress" flag
- The fetching thread sets this when it has new data
- The progress bar thread checks this flag
-
"completed" flag
- The fetching thread sets this when it finishes
- The other two threads check this flag
-
Potential Data Race
- Multiple threads
- Modification
-
Use mutexes
Hot Loop
- We need to lock the mutex while checking a bool
// In progress bar task function
std::lock_guard data_lck(data_mutex);
while (!update_progress) {}
-
The thread will run flat out
- The processor core will run at 100%
- Other threads cannot do useful work
- Uses a lot of electricity
-
The fetcher thread cannot set the flag
Hot Loop Avoidance
- To avoid this, unlock the mutex inside the Loop
std::unique_lock<std::mutex> data_lck(data_mutex);
while (!update_progress) {
data_lck.unlock();
std::this_thread::sleep_for(10ms);
data_lck.lock();
}
- Sleeping allows other threads to use the core
- The fetcher thread can set the flag
#include <cstddef>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
using namespace std::literals;
std::string sdata;
bool update_progress = false;
bool completed = false;
std::mutex data_mutex;
std::mutex completed_mutex;
// download thread
void fetch_data() {
for (int i = 0; i < 5; ++i) {
std::cout << "Fetcher thread waiting for data...\n";
std::this_thread::sleep_for(2s);
std::lock_guard<std::mutex> data_lck(data_mutex);
sdata += "Block" + std::to_string(i + 1);
std::cout << "sdata: " << sdata << '\n';
update_progress = true;
}
std::cout << "Fetch data has ended\n";
// Tell the progress bar thread to exit and wake up the processing thread
std::lock_guard<std::mutex> completed_lck(completed_mutex);
completed = true;
}
// progress bar thread
void progress_bar() {
size_t len = 0;
while (true) {
std::cout << "Progress bar thread waiting for data...\n";
// Wait until there is some new data to display
std::unique_lock<std::mutex> data_lck(data_mutex);
while (!update_progress) {
data_lck.unlock();
std::this_thread::sleep_for(10ms);
data_lck.lock();
}
len = sdata.size();
update_progress = false;
data_lck.unlock();
std::cout << "Received " << len << " bytes so far\n";
// Terminate when download has finished
std::lock_guard<std::mutex> completed_lck(completed_mutex);
if (completed) {
std::cout << "Progress bar thread has ended\n";
break;
}
}
}
void process_data() {
std::cout << "Processing thread waiting for data...\n";
// Wait until download is complete
std::unique_lock<std::mutex> completed_lck(completed_mutex);
while (!completed) {
completed_lck.unlock();
std::this_thread::sleep_for(10ms);
completed_lck.lock();
}
completed_lck.unlock();
std::lock_guard<std::mutex> data_lck(data_mutex);
std::cout << "Processing sdata: " << sdata << '\n';
}
int main() {
std::thread fetcher(fetch_data);
std::thread prog(progress_bar);
std::thread processor(process_data);
fetcher.join();
prog.join();
processor.join();
}
Implementation with Mutex
-
This is not ideal
- Too many loops
- Too much explicit locking and unlocking
- How do we choose the sleep duration?
-
Better solution
- Thread A indicates that it is waiting for something
- Thread B does the "something"
- Thread A is woken up and resumes