forked from CaptGreg/SenecaOOP345-attic
/
thread-schedule.cpp
190 lines (168 loc) · 7.4 KB
/
thread-schedule.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// http://codereview.stackexchange.com/questions/28437/multithreaded-task-scheduler
// This is (supposedly) a multi-threaded scheduler for one-time and/or repeating tasks. The tasks are simple std::function<void()> objects. I built it to be a crucial part of a larger project I'm working on, but I developed it stand-alone, so no context is missing for a review.
//
// I'm making heavy use of C++11 language and library features (especially thread support and chrono stuff).
//
// Tasks are supposed to be scheduled by specifying a start time_point, or a delay (converted to a time_point by adding it to now().) An optional duration specifies repeat intervals for the task (if it's non-zero).
//
// It should be possible to de-schedule tasks, preventing them from being started for execution from then on. (Already running tasks won't be stopped, to keep things a bit simpler, and also because I couldn't figure out a clean way to do it anyway.)
//
// I've never done anything with multithreading of this scale/complexity, and in case my brain never recovers from repeatedly being torn into 5 or more threads, I'd like to get some review/feedback from others. Specifically, race conditions/deadlocks/other threading-unpleasantness I didn't spot, lifetime issues, or really anything problematic.
//
// Some simple code at the very bottom demonstrates how it's meant to be used. It seemed to work when compiled with clang 3.3 and libc++.
#include <chrono>
#include <condition_variable>
#include <deque>
#include <list>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
#include <algorithm> // GB find
namespace scheduling {
template <class Clock>
class Scheduler {
typedef Clock clock_type;
typedef typename clock_type::time_point time_point;
typedef typename clock_type::duration duration;
typedef std::function<void()> task_type;
private:
struct Task {
public:
Task (task_type&& task, const time_point& start, const duration& repeat) : task(std::move(task)), start(start), repeat(repeat) { }
task_type task;
time_point start;
duration repeat;
bool operator<(const Task& other) const {
return start < other.start;
}
};
public:
typedef typename std::list<Task>::iterator task_handle;
private:
std::mutex mutex;
std::condition_variable tasks_updated;
std::deque<task_handle> todo;
std::condition_variable modified;
bool running;
std::list<Task> tasks;
std::list<task_handle> handles;
std::vector<std::thread> threads;
public:
Scheduler() : threads(4) {
}
~Scheduler() {
halt();
}
task_handle schedule(task_type&& task, const time_point& start, const duration& repeat=duration::zero()) {
task_handle h;
{
std::lock_guard<std::mutex> lk(mutex);
h = tasks.emplace(tasks.end(), std::move(task), start, repeat);
handles.push_back(h);
}
tasks_updated.notify_all();
return h;
}
task_handle schedule(task_type&& task, const duration& delay=duration::zero(), const duration& repeat=duration::zero()) {
return schedule(std::move(task, clock_type::now()+delay, repeat));
}
void unschedule(const task_handle& handle) {
{
std::lock_guard<std::mutex> lk(mutex);
auto handle_it = std::find(handles.begin(), handles.end(), handle);
if (handle_it != handles.end()) {
tasks.erase(handle);
todo.remove(handle);
handles.erase(handle_it);
}
}
tasks_updated.notify_all();
}
void clear() {
{
std::lock_guard<std::mutex> lk(mutex);
tasks.clear();
handles.clear();
}
tasks_updated.notify_all();
}
void run() {
{
std::lock_guard<std::mutex> lk(mutex);
if (running) return;
running = true;
for (auto& t : threads) {
t = std::thread([this]{this->loop();});
}
}
while (true) {
std::unique_lock<std::mutex> lk(mutex);
if (!running) break;
auto task_it = min_element(tasks.begin(), tasks.end());
time_point next_task = task_it == tasks.end() ? clock_type::time_point::max() : task_it->start;
if (tasks_updated.wait_until(lk, next_task) == std::cv_status::timeout) {
if (task_it->repeat != clock_type::duration::zero()) {
task_it->start += task_it->repeat;
}
else {
handles.remove(task_it);
tasks.erase(task_it);
}
todo.push_back(task_it);
modified.notify_all();
}
}
for (auto& t : threads) {
t.join();
}
}
void halt() {
{
std::lock_guard<std::mutex> lk(mutex);
if (!running) return;
running = false;
}
tasks_updated.notify_all();
modified.notify_all();
}
private:
void loop() {
while (true) {
std::function<void()> f;
{
std::unique_lock<std::mutex> lk(mutex);
while (todo.empty() && running) {
modified.wait(lk);
}
if (!running) {
return;
}
f = todo.front()->task;
todo.pop_front();
}
f();
}
}
};
}
#include <iostream>
void outp(const std::string& outp) {
static std::mutex m;
std::lock_guard<std::mutex> lk(m);
std::cout << std::this_thread::get_id() << ": " << outp << "\n";
}
int main(int argc, char* argv[]) {
scheduling::Scheduler<std::chrono::steady_clock> sched;
sched.schedule([&sched]{outp("Task 1");}, std::chrono::steady_clock::now());
sched.schedule([&sched]{outp("Task 2");}, std::chrono::steady_clock::now()+std::chrono::seconds(2), std::chrono::seconds(2));
sched.schedule([&sched]{outp("Task 3");}, std::chrono::steady_clock::now()+std::chrono::seconds(2), std::chrono::seconds(2));
sched.schedule([&sched]{outp("Task 4");}, std::chrono::steady_clock::now()+std::chrono::seconds(2), std::chrono::seconds(2));
sched.schedule([&sched]{outp("Task 5");}, std::chrono::steady_clock::now()+std::chrono::seconds(2), std::chrono::seconds(2));
sched.schedule([&sched]{outp("Task 6");}, std::chrono::steady_clock::now()+std::chrono::seconds(3));
sched.schedule([&sched]{outp("Task 7");}, std::chrono::steady_clock::now()+std::chrono::seconds(3));
sched.schedule([&sched]{outp("Task 8");}, std::chrono::steady_clock::now()+std::chrono::seconds(3));
sched.schedule([&sched]{outp("Task 9");}, std::chrono::steady_clock::now()+std::chrono::seconds(3));
sched.schedule([&sched]{outp("Task 10"); sched.halt(); }, std::chrono::steady_clock::now()+std::chrono::seconds(5));
sched.run();
}