-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmultithreading_dag.cpp
More file actions
283 lines (243 loc) · 8.4 KB
/
multithreading_dag.cpp
File metadata and controls
283 lines (243 loc) · 8.4 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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/*
一个轻量级、线程安全(多线程执行)、基于拓扑排序的有向无环图
*/
#include <chrono>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <stdexcept>
#include <thread>
#include <vector>
/**
* @brief 表示有向无环图中的一个任务节点
*
*/
class Node{
public:
int id; //< 节点唯一标识符
std::function<void()> task; //< 该节点要执行的任务
std::vector<Node*> successors; //< 该节点的完成后可以触发的后继节点
int inDegree = 0; //< 入度:当前尚未完成的前置依赖数量
Node(int id, std::function<void()> task):id(id),task(std::move(task)){}
/**
* @brief 添加一个后继节点,bing
*/
void addSuccessor(Node* succ){
if(succ){
successors.push_back(succ);
++succ->inDegree;
}
}
};
/**
* @brief DAG的执行器
* 使用工作线程池并发执行入度为0的就绪节点
* 节点完成后,减少后继节点的入度,若入度为零则加入就绪队列
* 支持超时控制、异常捕获、循环检测和优雅关闭
*/
class DAGExecutor{
private:
// 所有节点的所有权由 ownedNodes 管理 (RAII)
std::vector<std::unique_ptr<Node>> ownedNodes;
// 就绪队列
std::queue<Node*> readyQueue;
// 保护共享状态(readQueue,completedCount,shouldStop等)的互斥锁
mutable std::mutex mtx;
// 条件变量,用于线程等待就绪任务或停止信号
std::condition_variable cv;
int completedCount = 0; // 已完成的节点数量
int totalNodes = 0; // 总节点数
bool shouldStop = false; // 停止标示: 通知工作线程退出
// 工作线程池
std::vector<std::thread> workers;
/**
* @brief 工作线程主循环函数
*/
void worker(){
while(true){
Node *node = nullptr;
{
std::unique_lock<std::mutex> lock(mtx);
//等待: 要么有任务,要么收到停止信号
cv.wait(lock, [this](){return !readyQueue.empty() || shouldStop;});
if(shouldStop && readyQueue.empty()){
break;//安全退出
}
if(!readyQueue.empty()){
node = readyQueue.front(); // 获取到节点
readyQueue.pop();
}
}
if(node){
try{
node->task();// 执行任务,可能会抛出异常
} catch (const std::exception &e){
std::cerr << "[DAG] Node " << node->id << " threw: " << e.what() << "\n";
// 继续执行其他节点,不中断整个DAG
}
// 更新依赖关系 需要加锁
std::lock_guard<std::mutex> lock(mtx);
++completedCount;
for(Node* succ: node->successors){
--succ->inDegree;
if(succ->inDegree == 0){
readyQueue.push(succ);// 新就绪节点
}
}
cv.notify_all(); // 唤醒等待的线程
}
}
}
public:
/**
* @brief 构造函数
* @param numThreads线程数,默认为硬件并发数 std::thread::hardware_concurrency()
* 若numThreads为0,则设置为1
*/
explicit DAGExecutor(size_t numThreads = std::thread::hardware_concurrency()){
if(numThreads == 0){
numThreads = 1;
}
workers.reserve(numThreads);
for(size_t i = 0; i < numThreads; ++i){
workers.emplace_back(&DAGExecutor::worker, this);
}
}
/**
* 析构函数:确保线程安全退出
* 如果 workers 尚未清理(即未调用过 executeAndWait 成功完成)
* 则主动请求关闭 并 join 所有线程,防止资源泄露或者 terminate
*/
~DAGExecutor(){
if(!workers.empty()){
requestShutdown();
for(auto& t:workers){
if(t.joinable()){
t.join();
}
}
}
}
/**
* @brief 创建一个新节点并纳入管理
* @tparam F 可调用类型(支持lambda,函数指针等),万能引用
* @param task 任务函数
* @return 返回裸指针,所有权仍然归 ownedNodes
*/
template<typename F>
Node *createNode(int id, F &&task){
auto node = std::make_unique<Node>(id,std::forward<F>(task));
Node* raw = node.get();
ownedNodes.push_back(std::move(node));
return raw;
}
void executeAndWait(std::chrono::milliseconds timeout = std::chrono::seconds(30)){
if(workers.empty()){
throw std::runtime_error("Executor is already shut down");
}
// 收集所有节点指针 ownedNodes是unique_ptr, 这里取raw ptr
std::vector<Node*> nodes;
nodes.reserve(ownedNodes.size());
for(auto &node: ownedNodes){
nodes.push_back(node.get());
}
{
std::lock_guard<std::mutex> lock(mtx);
completedCount = 0;
shouldStop = false;
totalNodes = static_cast<int>(nodes.size());
if(totalNodes == 0){
return; // 无任务直接返回
}
// 清空可能残留的旧任务
while(!readyQueue.empty()){
readyQueue.pop();
}
bool hasReady = false;
for(Node* node :nodes){
if(node->inDegree == 0){ // 入度为0,加入就绪队列
readyQueue.push(node);
hasReady = true;
}
}
if(!hasReady){
shouldStop = true;
cv.notify_all();
throw std::runtime_error("Dag cycle detected: no node with in-degree zero!");
}
cv.notify_all();// 唤醒工作线程开始执行
}
// 等待全部完成或超时
std::unique_lock<std::mutex> lock(mtx);
bool finished = cv.wait_for(lock, timeout, [this](){ return completedCount >= totalNodes;}); // >= 防御性编程
if(!finished){
//超时处理
shouldStop = true;
cv.notify_all();
lock.unlock();
// 强制清理线程
for(auto &t: workers){
if(t.joinable()){
t.join();
}
}
workers.clear();// 标记已 shutdown
throw std::runtime_error("Dag execution timed out! completed:" + std::to_string(completedCount) + "/"
+ std::to_string(totalNodes) + ". Possible cycle or missing dependency");
}
// 正常完成: 关闭线程池
shouldStop = true;
cv.notify_all();
lock.unlock();
for(auto &t: workers){
if(t.joinable()){
t.join();
}
}
workers.clear(); // 避免析构时再次join
}
public:
/**
* @brief 请求线程池 停止(非阻塞)
* 设置shouldStop = true; 并通知所有等待线程
* 通常用于外部提前终止或者程序退出
*
*/
void requestShutdown(){
std::lock_guard<std::mutex> lock(mtx);
shouldStop = true;
cv.notify_all();
}
};
int main() {
DAGExecutor executor(4);
auto* A = executor.createNode(1, [] {
std::cout << "[A] Start\n";
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "[A] Done\n";
});
auto* B = executor.createNode(2, [] {
std::cout << "[B] Start\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "[B] Done\n";
});
auto* C = executor.createNode(3, [] {
std::cout << "[C] Start\n";
std::this_thread::sleep_for(std::chrono::milliseconds(150));
std::cout << "[C] Done\n";
});
A->addSuccessor(B);
A->addSuccessor(C);
try {
std::cout << "🚀 Executing DAG...\n";
executor.executeAndWait(std::chrono::seconds(5));
std::cout << "✅ All done!\n";
} catch (const std::exception& e) {
std::cerr << "❌ Error: " << e.what() << "\n";
return 1;
}
return 0;
}