Fix Memory Leak and Logic Errors in a Multi-threaded Task Scheduler
You are given a flawed C++ implementation of a simple multi-threaded task scheduler that manages and executes tasks concurrently. The code contains memory leaks, race conditions, and logic errors that cause incorrect task execution and resource cleanup issues. Your job is to identify and fix the bugs to produce a robust, leak-free, and correctly functioning scheduler.
Challenge prompt
The provided code implements a TaskScheduler class that allows scheduling and executing tasks concurrently using std::thread. The implementation should allow adding tasks and then start execution, waiting for all tasks to finish. However, there are multiple issues including memory leaks, race conditions, and incorrect handling of thread lifecycle causing crashes or undefined behavior. Fix the bugs so that the scheduler can safely execute all tasks concurrently and clean up all resources properly.
Guidance
- • Carefully review how std::thread objects are created, stored, and joined to prevent detached threads or dangling resources.
- • Investigate potential data races on shared containers used to store tasks and threads; consider applying appropriate synchronization or safer data structures.
- • Use smart pointers or containers to ensure automatic cleanup of dynamically allocated memory and prevent leaks.
Hints
- • Avoid storing threads as raw pointers and consider using joinable checks before joining threads.
- • Make sure no task is lost or overwritten in the container due to multi-threaded access or invalid iterators.
- • Check whether the vector holding threads is growing unexpectedly or if threads are joined multiple times.
Starter code
#include <iostream>
#include <vector>
#include <thread>
#include <functional>
class TaskScheduler {
private:
std::vector<std::thread*> threads;
std::vector<std::function<void()>> tasks;
public:
void addTask(std::function<void()> task) {
tasks.push_back(task);
}
void run() {
for (auto& task : tasks) {
std::thread* t = new std::thread(task);
threads.push_back(t);
}
for (auto& t : threads) {
t->join();
// Missing delete on t
}
}
~TaskScheduler() {
// Destructor is empty, leading to potential leaks
}
};
int main() {
TaskScheduler scheduler;
for (int i = 0; i < 5; ++i) {
scheduler.addTask([i]() { std::cout << "Task " << i << " executed" << std::endl; });
}
scheduler.run();
return 0;
}Expected output
Task 0 executed Task 1 executed Task 2 executed Task 3 executed Task 4 executed
Core concepts
Challenge a Friend
Send this duel to someone else and see if they can solve it.