Vince's CSV Parser
Loading...
Searching...
No Matches
indexed_task_pool.hpp
1#pragma once
2
3#include "../common.hpp"
4
5#include <atomic>
6#include <exception>
7#include <functional>
8#include <utility>
9
10#if CSV_ENABLE_THREADS
11#include <condition_variable>
12#include <mutex>
13#include <thread>
14#include <vector>
15#endif
16
17namespace csv {
18 namespace internals {
19 namespace parallel {
20
22 public:
23 explicit IndexedTaskPool(size_t worker_count)
24#if CSV_ENABLE_THREADS
25 : worker_count_(worker_count)
26#endif
27 {
28 this->start_workers(worker_count);
29 }
30
31 IndexedTaskPool(const IndexedTaskPool&) = delete;
32 IndexedTaskPool& operator=(const IndexedTaskPool&) = delete;
33
35 this->stop_workers();
36 }
37
38 size_t worker_count() const noexcept {
39#if CSV_ENABLE_THREADS
40 return this->worker_count_;
41#else
42 return 0;
43#endif
44 }
45
46 template<typename Fn>
47 void parallel_for(size_t task_count, Fn&& fn) {
48 if (task_count == 0) {
49 return;
50 }
51
52#if CSV_ENABLE_THREADS
53 if (this->workers_.empty()) {
54 this->run_serial(task_count, std::forward<Fn>(fn));
55 return;
56 }
57
58 std::exception_ptr captured_exception;
59 {
60 std::unique_lock<std::mutex> lock(this->mutex_);
61 this->current_task_ = std::forward<Fn>(fn);
62 this->task_exception_ = nullptr;
63 this->next_task_.store(0);
64 this->task_count_ = task_count;
65 this->active_workers_ = this->workers_.size();
66 ++this->generation_;
67 }
68
69 this->task_ready_.notify_all();
70
71 {
72 std::unique_lock<std::mutex> lock(this->mutex_);
73 this->task_done_.wait(lock, [this]() {
74 return this->completed_generation_ == this->generation_;
75 });
76
78 this->current_task_ = std::function<void(size_t, size_t)>();
79 this->task_exception_ = nullptr;
80 }
81
82#if CSV_EXCEPTIONS_ENABLED
84 std::rethrow_exception(captured_exception);
85 }
86#endif
87#else
88 this->run_serial(task_count, std::forward<Fn>(fn));
89#endif
90 }
91
92 private:
93 template<typename Fn>
94 void run_serial(size_t task_count, Fn&& fn) {
95 for (size_t task_index = 0; task_index < task_count; ++task_index) {
96 fn(0, task_index);
97 }
98 }
99
100#if CSV_ENABLE_THREADS
101 size_t worker_count_ = 0;
102
103 void start_workers(size_t worker_count) {
104 if (worker_count <= 1) {
105 return;
106 }
107
108 this->workers_.reserve(worker_count);
109 for (size_t worker_index = 0; worker_index < worker_count; ++worker_index) {
110 this->workers_.push_back(std::thread(&IndexedTaskPool::worker_loop, this, worker_index));
111 }
112 }
113
114 void stop_workers() {
115 {
116 std::lock_guard<std::mutex> lock(this->mutex_);
117 this->stop_ = true;
118 ++this->generation_;
119 }
120
121 this->task_ready_.notify_all();
122 for (size_t i = 0; i < this->workers_.size(); ++i) {
123 if (this->workers_[i].joinable()) {
124 this->workers_[i].join();
125 }
126 }
127 }
128
129 void worker_loop(size_t worker_index) {
130 size_t seen_generation = 0;
131
132 for (;;) {
133 {
134 std::unique_lock<std::mutex> lock(this->mutex_);
135 this->task_ready_.wait(lock, [this, &seen_generation]() {
136 return this->stop_ || this->generation_ != seen_generation;
137 });
138
139 if (this->stop_) {
140 return;
141 }
142
143 seen_generation = this->generation_;
144 }
145
146 for (;;) {
147 const size_t task_index = this->next_task_.fetch_add(1);
148 if (task_index >= this->task_count_) {
149 break;
150 }
151
152 CSV_TRY {
154 }
155 CSV_CATCH((...), {
156 std::lock_guard<std::mutex> lock(this->mutex_);
157 if (!this->task_exception_) {
158 this->task_exception_ = std::current_exception();
159 this->next_task_.store(this->task_count_);
160 }
161 break;
162 })
163 }
164
165 {
166 std::lock_guard<std::mutex> lock(this->mutex_);
167 if (--this->active_workers_ == 0) {
169 this->task_done_.notify_one();
170 }
171 }
172 }
173 }
174
175 std::vector<std::thread> workers_;
176 std::mutex mutex_;
177 std::condition_variable task_ready_;
178 std::condition_variable task_done_;
179 std::function<void(size_t, size_t)> current_task_;
180 std::exception_ptr task_exception_ = nullptr;
181 std::atomic<size_t> next_task_{0};
182 size_t task_count_ = 0;
183 size_t active_workers_ = 0;
184 size_t generation_ = 0;
185 size_t completed_generation_ = 0;
186 bool stop_ = false;
187#else
188 void start_workers(size_t) {}
189 void stop_workers() {}
190#endif
191 };
192
193 }
194 }
195}
Borrow a row and error state for synchronous, single-pass field access.
The all encompassing namespace.