Skip to content

Commit 120adbd

Browse files
committed
fix(thread-pool): cap workers at hardware concurrency
1 parent bdcb088 commit 120adbd

2 files changed

Lines changed: 47 additions & 6 deletions

File tree

src/ailego/parallel/thread_pool.cc

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#include <algorithm>
1516
#include <zvec/ailego/logger/logger.h>
1617
#include <zvec/ailego/parallel/thread_pool.h>
1718

@@ -80,12 +81,42 @@ static inline void UnbindThreads(std::vector<std::thread> &) {}
8081
namespace zvec {
8182
namespace ailego {
8283

84+
namespace {
85+
86+
uint32_t SafeWorkerCount() noexcept {
87+
return std::max(std::thread::hardware_concurrency(), 1u);
88+
}
89+
90+
} // namespace
91+
8392
ThreadPool::ThreadPool(uint32_t size, bool binding) {
84-
for (uint32_t i = 0u; i < size; ++i) {
85-
pool_.emplace_back(&ThreadPool::worker, this);
93+
const uint32_t max_size = SafeWorkerCount();
94+
const uint32_t safe_size = std::min(size, max_size);
95+
if (safe_size != size) {
96+
LOG_WARN(
97+
"ThreadPool worker count[%u] exceeds available CPUs[%u], "
98+
"clamped to %u",
99+
size, max_size, safe_size);
86100
}
87-
if (binding) {
88-
this->bind();
101+
102+
try {
103+
// Avoid vector reallocations after workers have started. If thread creation
104+
// still fails, stop and join the workers already created before rethrowing.
105+
pool_.reserve(safe_size);
106+
for (uint32_t i = 0u; i < safe_size; ++i) {
107+
pool_.emplace_back(&ThreadPool::worker, this);
108+
}
109+
if (binding) {
110+
this->bind();
111+
}
112+
} catch (...) {
113+
this->stop();
114+
for (auto &worker : pool_) {
115+
if (worker.joinable()) {
116+
worker.join();
117+
}
118+
}
119+
throw;
89120
}
90121
}
91122

tests/ailego/parallel/thread_pool_test.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#include <algorithm>
1516
#include <chrono>
1617
#include <iostream>
18+
#include <limits>
1719
#include <memory>
1820
#include <type_traits>
1921
#include <gtest/gtest.h>
@@ -42,8 +44,16 @@ TEST(ThreadPool, ConstructorBehavior) {
4244
ThreadPool single_unbound_pool(1, false);
4345
EXPECT_EQ(1u, single_unbound_pool.count());
4446

45-
ThreadPool double_bound_pool(2, true);
46-
EXPECT_EQ(2u, double_bound_pool.count());
47+
const uint32_t bound_count = std::min(hardware_concurrency, 2u);
48+
ThreadPool bound_pool(bound_count, true);
49+
EXPECT_EQ(bound_count, bound_pool.count());
50+
}
51+
52+
TEST(ThreadPool, CapsExcessiveWorkerCount) {
53+
const auto hardware_concurrency =
54+
std::max(std::thread::hardware_concurrency(), 1u);
55+
ThreadPool pool((std::numeric_limits<uint32_t>::max)(), false);
56+
EXPECT_EQ(hardware_concurrency, pool.count());
4757
}
4858

4959
#if defined(__linux__) && !defined(__ANDROID__)

0 commit comments

Comments
 (0)