-
Notifications
You must be signed in to change notification settings - Fork 0
/
thread_example.cpp
173 lines (144 loc) · 6.39 KB
/
thread_example.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
//=================================================================================================
// Copyright (C) 2023 GRAPE Contributors
//=================================================================================================
#include <cmath>
#include <csignal>
#include <cstring>
#include <print>
#include "grape/realtime/schedule.h"
#include "grape/realtime/thread.h"
namespace {
std::atomic_flag s_exit = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
//-------------------------------------------------------------------------------------------------
// signal handler
void onSignal(int /*signum*/) {
s_exit.test_and_set();
s_exit.notify_one();
}
//-------------------------------------------------------------------------------------------------
// A statistics accumulator
class Profiler {
public:
struct Stats {
std::uint64_t num_samples{};
double abs_max{};
double mean{};
double variance{};
};
void addSample(double sample);
[[nodiscard]] auto stats() const -> const Stats&;
private:
Stats stats_;
double ssd_{ 0 }; //!< sum of square of differences
};
} // namespace
//=================================================================================================
// Example program demonstrates the recommended approach to building a realtime application.
// - Delegates realtime execution path to a separate thread. (which just accumulates timing stats)
// - Main thread continues unprivileged and handles events, I/O, user inputs, etc.
auto main() -> int {
std::ignore = signal(SIGINT, onSignal);
std::ignore = signal(SIGTERM, onSignal);
try {
// disable swap
const auto is_mem_locked = grape::realtime::lockMemory();
if (is_mem_locked.code != 0) {
std::println("Main thread: {}: {}. Continuing ..", //
is_mem_locked.function_name, //
strerror(is_mem_locked.code)); // NOLINT(concurrency-mt-unsafe)
}
// create task configurator
auto task_config = grape::realtime::Thread::Config();
// set task thread identifier
task_config.name = "rt_task";
// set task update interval
static constexpr auto PROCESS_INTERVAL = std::chrono::microseconds(1000);
task_config.interval = PROCESS_INTERVAL;
// Define CPU cores to allocate to non-rt and rt threads. Ideally these should be
// non-intersecting sets.
static constexpr auto CPUS_RT = { 0U };
static constexpr auto CPUS_NON_RT = { 1U, 2U, 3U };
// Set main thread CPU affinity here. Will assign rt thread CPU affinity in task setup().
const auto is_main_cpu_set = grape::realtime::setCpuAffinity(CPUS_NON_RT);
if (is_main_cpu_set.code != 0) {
std::println("Main thread: {}: {}. Continuing ..", //
is_main_cpu_set.function_name, //
strerror(is_main_cpu_set.code)); // NOLINT(concurrency-mt-unsafe)
} else {
std::println("Main thread: Set to run on CPUs {}", CPUS_NON_RT);
}
// set task thread to run on a specific CPU with real-time scheduling policy
task_config.setup = []() -> bool {
std::println("Setup started");
const auto is_task_cpu_set = grape::realtime::setCpuAffinity(CPUS_RT);
if (is_task_cpu_set.code != 0) {
std::println("Task thread: {}: {}. Continuing ..", //
is_task_cpu_set.function_name, //
strerror(is_task_cpu_set.code)); // NOLINT(concurrency-mt-unsafe)
} else {
std::println("Task thread: Set to run on CPUs {}", CPUS_RT);
}
static constexpr auto RT_PRIORITY = 20;
const auto is_scheduled =
grape::realtime::setSchedule({ .policy = grape::realtime::Schedule::Policy::Realtime, //
.priority = RT_PRIORITY });
if (is_scheduled.code != 0) {
std::println("Task thread: {}: {}. Continuing ..", //
is_scheduled.function_name, //
strerror(is_scheduled.code)); // NOLINT(concurrency-mt-unsafe)
} else {
std::println("Task thread: Scheduled to run at RT priority {}", RT_PRIORITY);
}
std::println("Setup done");
return true;
};
Profiler profiler;
// set the periodic process function for the task thread
task_config.process = [&profiler]() -> bool {
const auto tp = grape::realtime::Thread::ProcessClock::now();
static auto last_tp = tp;
const auto dt = std::chrono::duration<double>(tp - last_tp).count();
last_tp = tp;
profiler.addSample(dt);
const auto stats = profiler.stats();
std::print("\rProcess step={:06d}, dt={:.6f}, max={:.6f}, mean={:.6f}, std.dev.={:.9f}",
stats.num_samples, dt, stats.abs_max, stats.mean, std::sqrt(stats.variance));
return true;
};
// set the clean up function for the task thread
task_config.teardown = []() { std::println("\nTeardown"); };
// off we go. start the task
auto task = grape::realtime::Thread(std::move(task_config));
task.start();
// main thread continues to handle regular tasks such as event handling
std::println("\nPress ctrl-c to exit");
s_exit.wait(false);
// Send request to exit thread
task.stop();
// print results from task before exit
const auto& stats = profiler.stats();
std::println(
"Final process timing statistics: max={:.6f}, mean={:.6f}, std.dev.={:.9f} ({} samples)",
stats.abs_max, stats.mean, std::sqrt(stats.variance), stats.num_samples);
} catch (...) {
grape::Exception::print();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
//-------------------------------------------------------------------------------------------------
inline void Profiler::addSample(double sample) {
// Numerically stable version of Welford's online algorithm from
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
stats_.abs_max = std::max(std::abs(sample), stats_.abs_max);
stats_.num_samples += 1;
const auto delta = sample - stats_.mean;
stats_.mean += delta / static_cast<double>(stats_.num_samples);
const auto delta2 = sample - stats_.mean;
ssd_ += delta * delta2;
stats_.variance = ssd_ / static_cast<double>(stats_.num_samples - 1);
}
//-------------------------------------------------------------------------------------------------
inline auto Profiler::stats() const -> const Profiler::Stats& {
return stats_;
}