-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathmain.cpp
More file actions
176 lines (154 loc) · 7.6 KB
/
main.cpp
File metadata and controls
176 lines (154 loc) · 7.6 KB
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
#include "Thread.h"
#include <iostream>
#include <chrono>
// @see https://github.com/endurodave/StdWorkerThread
// David Lafreniere
// -----------------------------------------------------------------------------
// This example demonstrates a C++17 worker thread implementation built on
// std::thread with three key features over a basic event loop:
//
// 1. Priority Queue
// Messages are held in a std::priority_queue rather than a plain
// std::queue. When multiple messages are queued simultaneously the worker
// thread always dequeues the highest-priority one first (HIGH > NORMAL >
// LOW), making it straightforward to give urgent work preferential access
// to the thread without a separate fast-path queue.
//
// 2. Back Pressure
// An optional maxQueueSize cap limits how many messages may sit in the
// queue at once. When the queue is full, PostMsg() blocks the calling
// thread until the worker drains at least one message. This prevents a
// fast producer from outrunning a slow consumer and exhausting memory.
//
// 3. Watchdog
// An optional timeout launches a lightweight watchdog thread alongside
// the worker. The worker updates a "last alive" timestamp on every loop
// iteration. If the timestamp goes stale — because the thread is stuck
// inside a handler (deadlock, infinite loop, etc.) — the watchdog logs
// an error. In a production system the watchdog callback can trigger a
// controlled shutdown or system reset instead.
// -----------------------------------------------------------------------------
using namespace std;
// Worker thread instances:
// workerThread1 — unlimited queue, used for the priority demo.
// workerThread2 — max 5 queued messages, used for the back pressure demo.
// workerThread3 — watchdog enabled, used for the watchdog demo.
// workerThread4 — unlimited queue, used for the timer demo.
// workerThread5 — max 5 queued messages, FullPolicy::DROP used for drop demo.
Thread workerThread1("WorkerThread1");
Thread workerThread2("WorkerThread2", 5);
Thread workerThread3("WorkerThread3");
Thread workerThread4("WorkerThread4");
Thread workerThread5("WorkerThread5", 5, FullPolicy::DROP);
//------------------------------------------------------------------------------
// main
//------------------------------------------------------------------------------
int main(void)
{
// Start all worker threads. Each thread signals via std::promise that
// it is fully running before CreateThread() returns, so it is safe to post
// messages immediately after this block.
workerThread1.CreateThread();
workerThread2.CreateThread();
workerThread3.CreateThread(std::chrono::milliseconds(2000)); // 2 s watchdog
workerThread4.CreateThread();
workerThread5.CreateThread();
// -------------------------------------------------------------------------
// DROP demo (WorkerThread5, maxQueueSize = 5, FullPolicy = DROP)
//
// Ten messages are posted to a thread whose queue holds at most 5. Once
// the queue is full, subsequent PostMsg() calls silently discard the
// message and return immediately without blocking. This is useful for
// best-effort telemetry or display updates.
// -------------------------------------------------------------------------
cout << "\n-- DROP demo (WorkerThread5, maxQueueSize=5, policy=DROP) --" << endl;
for (int i = 1; i <= 10; i++)
{
auto data = make_shared<UserData>();
data->msg = "Drop message #" + to_string(i);
data->year = 2026;
workerThread5.PostMsg(data); // returns immediately even if queue is full
}
// Three messages are posted in LOW -> NORMAL -> HIGH order. Because all
// three are enqueued before the worker has a chance to drain them, the
// priority queue reorders them so HIGH is processed first, then NORMAL,
// then LOW — regardless of the order they were posted.
// -------------------------------------------------------------------------
cout << "\n-- Priority queue demo (WorkerThread1) --" << endl;
auto lowData = make_shared<UserData>();
lowData->msg = "Low priority msg";
lowData->year = 2026;
workerThread1.PostMsg(lowData, Priority::LOW);
auto normalData = make_shared<UserData>();
normalData->msg = "Normal priority msg";
normalData->year = 2026;
workerThread1.PostMsg(normalData, Priority::NORMAL);
auto highData = make_shared<UserData>();
highData->msg = "High priority msg";
highData->year = 2026;
workerThread1.PostMsg(highData, Priority::HIGH);
// -------------------------------------------------------------------------
// Back pressure demo (WorkerThread2, maxQueueSize = 5)
//
// Ten messages are posted to a thread whose queue holds at most 5. Once
// the queue is full PostMsg() blocks the main thread until the worker
// processes a message and frees a slot. This cooperative throttling keeps
// memory use bounded without dropping any messages.
// -------------------------------------------------------------------------
cout << "\n-- Back pressure demo (WorkerThread2, maxQueueSize=5) --" << endl;
for (int i = 1; i <= 10; i++)
{
auto data = make_shared<UserData>();
data->msg = "Back pressure message #" + to_string(i);
data->year = 2026;
workerThread2.PostMsg(data); // blocks the caller when the queue is full
}
// -------------------------------------------------------------------------
// Watchdog demo (WorkerThread3, timeout = 2 000 ms)
//
// The thread is healthy here so the watchdog never fires. To observe a
// watchdog alert, add a long sleep or spin loop inside the MSG_POST_USER_DATA
// case in Thread.cpp — the watchdog will print an error after 2 seconds.
// -------------------------------------------------------------------------
cout << "\n-- Watchdog demo (WorkerThread3, watchdog=2000ms) --" << endl;
auto wdData = make_shared<UserData>();
wdData->msg = "Watchdog-monitored message";
wdData->year = 2026;
workerThread3.PostMsg(wdData);
// -------------------------------------------------------------------------
// Timer demo (WorkerThread4)
//
// A plain std::thread fires every 250ms and posts a LOW priority message
// to workerThread4. Back pressure works naturally here: if workerThread4
// fell behind,
// PostMsg() would block the timer thread rather than flooding the queue.
// The timer thread checks timerExit before each sleep so it exits cleanly
// without waiting for one last 250ms interval to expire.
// -------------------------------------------------------------------------
cout << "\n-- Timer demo (WorkerThread4, 250ms interval) --" << endl;
atomic<bool> timerExit(false);
thread timerThread([&]() {
while (!timerExit.load())
{
this_thread::sleep_for(chrono::milliseconds(250));
if (timerExit.load())
break;
auto data = make_shared<UserData>();
data->msg = "Timer expired";
data->year = 2026;
workerThread4.PostMsg(data, Priority::LOW);
}
});
// Let the timer fire a few times.
this_thread::sleep_for(chrono::milliseconds(1000));
// Stop the timer thread before exiting the worker thread so PostMsg()
// is never called after ExitThread().
timerExit.store(true);
timerThread.join();
workerThread1.ExitThread();
workerThread2.ExitThread();
workerThread3.ExitThread();
workerThread4.ExitThread();
workerThread5.ExitThread();
return 0;
}