Skip to content

Commit d4da058

Browse files
authored
Add threadpool (#212)
Add threadpool
1 parent af07efd commit d4da058

File tree

8 files changed

+344
-0
lines changed

8 files changed

+344
-0
lines changed

Code/max/Hardware/CPU/Task.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2025, The max Contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include <max/Hardware/CPU/Task.hpp>
6+
7+
namespace max {
8+
namespace Hardware {
9+
namespace CPU {
10+
11+
Task::~Task() noexcept = default;
12+
13+
} // namespace CPU
14+
} // namespace Hardware
15+
} // namespace max

Code/max/Hardware/CPU/Task.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2025, The max Contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef MAX_HARDWARE_CPU_TASK_HPP
6+
#define MAX_HARDWARE_CPU_TASK_HPP
7+
8+
namespace max {
9+
namespace Hardware {
10+
namespace CPU {
11+
12+
class Task {
13+
public:
14+
15+
virtual ~Task() noexcept;
16+
17+
virtual void Run() noexcept = 0;
18+
19+
};
20+
21+
} // namespace CPU
22+
} // namespace Hardware
23+
} // namespace max
24+
25+
#endif // #ifndef MAX_HARDWARE_CPU_TASK_HPP
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2025, The max Contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include <max/Hardware/CPU/TaskQueue.hpp>
6+
7+
#include <type_traits>
8+
9+
#include <utility>
10+
11+
namespace max {
12+
namespace Hardware {
13+
namespace CPU {
14+
15+
TaskQueue::TaskQueue(HANDLE wake_event) noexcept
16+
: task_queue_mutex_()
17+
, task_queue_()
18+
, wake_event_(std::move(wake_event))
19+
, shutting_down_(false)
20+
{
21+
}
22+
23+
TaskQueue::~TaskQueue() noexcept {
24+
BOOL result = CloseHandle(wake_event_);
25+
if (result == 0) {
26+
// GetLastError()
27+
}
28+
}
29+
30+
TaskQueue::AddTaskError TaskQueue::AddTask(std::unique_ptr<Task> task) noexcept {
31+
if (shutting_down_) {
32+
return AddTaskError::ShuttingDown;
33+
}
34+
35+
{
36+
std::lock_guard<std::mutex> task_queue_guard(task_queue_mutex_);
37+
task_queue_.push(std::move(task));
38+
BOOL result = SetEvent(wake_event_);
39+
if (result == 0) {
40+
// GetLastError()
41+
return AddTaskError::CouldNotSetEvent;
42+
}
43+
}
44+
45+
return AddTaskError::Okay;
46+
}
47+
48+
std::unique_ptr<Task> TaskQueue::TryPopTask() noexcept {
49+
std::lock_guard<std::mutex> task_queue_guard(task_queue_mutex_);
50+
51+
BOOL result = ResetEvent(wake_event_);
52+
if (result == 0) {
53+
// GetLastError
54+
return nullptr;
55+
}
56+
57+
if (task_queue_.empty()) {
58+
return nullptr;
59+
}
60+
61+
std::unique_ptr<Task> task = std::move(task_queue_.front());
62+
task_queue_.pop();
63+
return task;
64+
}
65+
66+
TaskQueue::WaitForEventError TaskQueue::WaitForEvent() noexcept {
67+
auto result = WaitForMultipleObjects(1, &wake_event_, TRUE, INFINITE);
68+
if (result != WAIT_OBJECT_0) {
69+
return WaitForEventError::CouldNotWait;
70+
} else {
71+
result = ResetEvent(wake_event_);
72+
if (result == 0) {
73+
// GetLastError()
74+
return WaitForEventError::CouldNotResetEvent;
75+
}
76+
}
77+
78+
return WaitForEventError::Okay;
79+
}
80+
81+
TaskQueue::ShutdownError TaskQueue::Shutdown() noexcept {
82+
std::lock_guard<std::mutex> task_queue_guard(task_queue_mutex_);
83+
84+
shutting_down_ = true;
85+
BOOL result = SetEvent(wake_event_);
86+
if (result == 0) {
87+
// GetLastError()
88+
return ShutdownError::CouldNotSetEvent;
89+
}
90+
91+
return ShutdownError::Okay;
92+
}
93+
94+
std::expected<std::unique_ptr<TaskQueue>, CreateTaskQueueError> CreateTaskQueue() noexcept {
95+
HANDLE wake_event = CreateEvent(NULL, TRUE, FALSE, TEXT("wake_event_"));
96+
if (wake_event == NULL) {
97+
// GetLastError()
98+
return std::unexpected(CreateTaskQueueError::CouldNotCreate);
99+
}
100+
101+
return std::make_unique<TaskQueue>(std::move(wake_event));
102+
}
103+
104+
void TaskRunnerLoop(TaskQueue* task_queue) noexcept {
105+
bool continue_looping = true;
106+
do {
107+
auto task = task_queue->TryPopTask();
108+
if (task) {
109+
task->Run();
110+
}
111+
else {
112+
if (task_queue->shutting_down_) {
113+
continue_looping = false;
114+
}
115+
else {
116+
task_queue->WaitForEvent();
117+
}
118+
}
119+
} while (continue_looping);
120+
}
121+
122+
} // namespace CPU
123+
} // namespace Hardware
124+
} // namespace max
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2025, The max Contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef MAX_HARDWARE_CPU_TASKQUEUE_HPP
6+
#define MAX_HARDWARE_CPU_TASKQUEUE_HPP
7+
8+
#include <expected>
9+
#include <memory>
10+
#include <mutex>
11+
#include <queue>
12+
13+
14+
// TODO: Handle other platforms
15+
// TODO: This is only needed for the definition of HANDLE. Can we remove this somehow?
16+
#ifndef WIN32_LEAN_AND_MEAN
17+
#define WIn32_LEAN_AND_MEAN
18+
#endif
19+
#include <Windows.h>
20+
21+
#include <max/Hardware/CPU/Task.hpp>
22+
23+
namespace max {
24+
namespace Hardware {
25+
namespace CPU {
26+
27+
// TaskQueue uses std::mutex, which cannot be copied nor moved.
28+
// This restriction has a major impact on the design of the class.
29+
// Instead of the usual std::expected<T, ErrorCode> CreateT() function,
30+
// we use std::expected<std::unique_ptr<T, ErrorCode> CreateT().
31+
//
32+
// The additional unique_ptr means the actual object doesn't have to move.
33+
34+
// Assume a 64-byte cache line size, and align to that.
35+
// This is so a vector of TaskQueues does not cause false sharing between threads.
36+
// TODO: VC warns that alignas(64) did indeed pad. And we treat warnings as errors.
37+
class /*alignas(64)*/ TaskQueue {
38+
public:
39+
40+
// TODO: This is only public so std::make_unique can call it
41+
TaskQueue(HANDLE wake_event) noexcept;
42+
~TaskQueue() noexcept;
43+
44+
enum class AddTaskError {
45+
Okay,
46+
ShuttingDown,
47+
CouldNotSetEvent,
48+
};
49+
AddTaskError AddTask(std::unique_ptr<Task> task) noexcept;
50+
51+
// TODO: Should this return a std::expected so it can provide error values?
52+
std::unique_ptr<Task> TryPopTask() noexcept;
53+
54+
enum class WaitForEventError {
55+
Okay,
56+
CouldNotWait,
57+
CouldNotResetEvent,
58+
};
59+
WaitForEventError WaitForEvent() noexcept;
60+
61+
enum class ShutdownError {
62+
Okay,
63+
CouldNotSetEvent,
64+
};
65+
ShutdownError Shutdown() noexcept;
66+
67+
volatile bool shutting_down_;
68+
69+
private:
70+
71+
std::mutex task_queue_mutex_;
72+
std::queue<std::unique_ptr<Task>> task_queue_;
73+
HANDLE wake_event_;
74+
75+
};
76+
77+
enum class CreateTaskQueueError {
78+
CouldNotCreate,
79+
};
80+
// TODO: Possibly provide an allocator, to allocate the TaskQueue at the right location
81+
std::expected<std::unique_ptr<TaskQueue>, CreateTaskQueueError> CreateTaskQueue() noexcept;
82+
83+
void TaskRunnerLoop(TaskQueue* task_queue) noexcept;
84+
85+
} // namespace CPU
86+
} // namespace Hardware
87+
} // namespace max
88+
89+
#endif // #ifndef MAX_HARDWARE_CPU_TASKQUEUE_HPP
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2025, The max Contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include <max/Hardware/CPU/TaskThread.hpp>
6+
7+
#include <utility>
8+
9+
namespace max {
10+
namespace Hardware {
11+
namespace CPU {
12+
13+
TaskThread::TaskThread(std::unique_ptr<max::Hardware::CPU::TaskQueue> task_queue, std::thread thread) noexcept
14+
: task_queue_(std::move(task_queue))
15+
, thread_(std::move(thread))
16+
{}
17+
18+
std::optional<TaskThread> CreateTaskThread() noexcept {
19+
auto create_task_queue_result = max::Hardware::CPU::CreateTaskQueue();
20+
if (!create_task_queue_result) {
21+
return std::nullopt;
22+
}
23+
std::unique_ptr<max::Hardware::CPU::TaskQueue> task_queue = std::move(*create_task_queue_result);
24+
25+
auto thread = std::thread(max::Hardware::CPU::TaskRunnerLoop, task_queue.get());
26+
// TODO: Attempt to lock that thread to the preferred type of core.
27+
28+
return TaskThread(std::move(task_queue), std::move(thread));
29+
}
30+
31+
} // namespace CPU
32+
} // namespace Hardware
33+
} // namespace max
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2025, The max Contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef MAX_HARDWARE_CPU_TASKTHREAD_HPP
6+
#define MAX_HARDWARE_CPU_TASKTHREAD_HPP
7+
8+
#include <memory>
9+
#include <optional>
10+
#include <thread>
11+
12+
#include <max/Hardware/CPU/TaskQueue.hpp>
13+
14+
namespace max {
15+
namespace Hardware {
16+
namespace CPU {
17+
18+
class TaskThread {
19+
public:
20+
21+
explicit TaskThread(std::unique_ptr<max::Hardware::CPU::TaskQueue> task_queue, std::thread thread) noexcept;
22+
23+
std::unique_ptr<max::Hardware::CPU::TaskQueue> task_queue_;
24+
std::thread thread_;
25+
26+
};
27+
28+
std::optional<TaskThread> CreateTaskThread() noexcept;
29+
30+
} // namespace CPU
31+
} // namespace Hardware
32+
} // namespace max
33+
34+
#endif // #ifndef MAX_HARDWARE_CPU_TASKTHREAD_HPP

Projects/VisualStudio/max/max.vcxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@
202202
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
203203
</ClInclude>
204204
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\Prefetch.hpp" />
205+
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\Task.hpp" />
206+
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TaskQueue.hpp" />
207+
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TaskThread.hpp" />
205208
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TLB.hpp" />
206209
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TraceCache.hpp" />
207210
<ClInclude Include="..\..\..\Code\max\Logging\DoNothingLogger.hpp" />
@@ -305,6 +308,9 @@
305308
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
306309
</ClCompile>
307310
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\Prefetch.cpp" />
311+
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\Task.cpp" />
312+
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TaskQueue.cpp" />
313+
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TaskThread.cpp" />
308314
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TLB.cpp" />
309315
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TraceCache.cpp" />
310316
<ClCompile Include="..\..\..\Code\max\Logging\DoNothingLogger.cpp" />

Projects/VisualStudio/max/max.vcxproj.filters

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,15 @@
393393
<ClInclude Include="..\..\..\Code\max\Compiling\Bitmask.hpp">
394394
<Filter>Code\max\Compiling</Filter>
395395
</ClInclude>
396+
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TaskThread.hpp">
397+
<Filter>Code\max\Hardware\CPU</Filter>
398+
</ClInclude>
399+
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\Task.hpp">
400+
<Filter>Code\max\Hardware\CPU</Filter>
401+
</ClInclude>
402+
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TaskQueue.hpp">
403+
<Filter>Code\max\Hardware\CPU</Filter>
404+
</ClInclude>
396405
</ItemGroup>
397406
<ItemGroup>
398407
<ClCompile Include="..\..\..\Code\max\Algorithms\IsBetweenTest.cpp">
@@ -479,6 +488,15 @@
479488
<ClCompile Include="..\..\..\Code\max\Containers\RectangleTest.cpp">
480489
<Filter>Code\max\Containers</Filter>
481490
</ClCompile>
491+
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TaskThread.cpp">
492+
<Filter>Code\max\Hardware\CPU</Filter>
493+
</ClCompile>
494+
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\Task.cpp">
495+
<Filter>Code\max\Hardware\CPU</Filter>
496+
</ClCompile>
497+
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TaskQueue.cpp">
498+
<Filter>Code\max\Hardware\CPU</Filter>
499+
</ClCompile>
482500
</ItemGroup>
483501
<ItemGroup>
484502
<MASM Include="..\..\..\Code\max\Hardware\CPU\IsCPUIDAvailablePolicies\max_Hardware_CPU_X64AssemblyIsCPUIDAvailablePolicies_IsCPUIDAvailable.asm">

0 commit comments

Comments
 (0)