2023-01-27 16:43:50 +01:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Thread pool for Verilator itself
|
|
|
|
|
//
|
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Copyright 2005-2023 by Wilson Snyder. This program is free software; you can
|
|
|
|
|
// redistribute it and/or modify it under the terms of either the GNU
|
|
|
|
|
// Lesser General Public License Version 3 or the Perl Artistic License
|
|
|
|
|
// Version 2.0.
|
|
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
|
|
|
|
|
#include "config_build.h"
|
|
|
|
|
|
|
|
|
|
#include "V3ThreadPool.h"
|
|
|
|
|
|
|
|
|
|
#include "V3Error.h"
|
|
|
|
|
|
|
|
|
|
// c++11 requires definition of static constexpr as well as declaration
|
|
|
|
|
constexpr unsigned int V3ThreadPool::FUTUREWAITFOR_MS;
|
|
|
|
|
|
2023-05-13 16:32:33 +02:00
|
|
|
void V3ThreadPool::resize(unsigned n) VL_MT_UNSAFE VL_EXCLUDES(m_mutex)
|
|
|
|
|
VL_EXCLUDES(m_stoppedJobsMutex) {
|
2023-01-27 16:43:50 +01:00
|
|
|
// This function is not thread-safe and can result in race between threads
|
2023-04-11 13:23:24 +02:00
|
|
|
UASSERT(V3MutexConfig::s().lockConfig(),
|
|
|
|
|
"Mutex config needs to be locked before starting ThreadPool");
|
2023-05-13 16:32:33 +02:00
|
|
|
{
|
|
|
|
|
V3LockGuard lock{m_mutex};
|
|
|
|
|
V3LockGuard stoppedJobsLock{m_stoppedJobsMutex};
|
|
|
|
|
|
|
|
|
|
UASSERT(m_queue.empty(), "Resizing busy thread pool");
|
|
|
|
|
// Shut down old threads
|
|
|
|
|
m_shutdown = true;
|
|
|
|
|
m_stoppedJobs = 0;
|
|
|
|
|
m_cv.notify_all();
|
|
|
|
|
m_stoppedJobsCV.notify_all();
|
|
|
|
|
}
|
2023-01-27 16:43:50 +01:00
|
|
|
while (!m_workers.empty()) {
|
|
|
|
|
m_workers.front().join();
|
|
|
|
|
m_workers.pop_front();
|
|
|
|
|
}
|
2023-05-13 16:32:33 +02:00
|
|
|
if (n > 1) {
|
|
|
|
|
V3LockGuard lock{m_mutex};
|
|
|
|
|
// Start new threads
|
|
|
|
|
m_shutdown = false;
|
|
|
|
|
for (unsigned int i = 1; i < n; ++i) {
|
|
|
|
|
m_workers.emplace_back(&V3ThreadPool::startWorker, this, i);
|
|
|
|
|
}
|
2023-01-27 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void V3ThreadPool::startWorker(V3ThreadPool* selfThreadp, int id) VL_MT_SAFE {
|
|
|
|
|
selfThreadp->workerJobLoop(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void V3ThreadPool::workerJobLoop(int id) VL_MT_SAFE {
|
|
|
|
|
while (true) {
|
|
|
|
|
// Wait for a notification
|
|
|
|
|
waitIfStopRequested();
|
2023-06-24 00:25:12 +02:00
|
|
|
VAnyPackagedTask job;
|
2023-01-27 16:43:50 +01:00
|
|
|
{
|
2023-04-11 13:23:24 +02:00
|
|
|
V3LockGuard lock(m_mutex);
|
2023-05-13 16:32:33 +02:00
|
|
|
m_cv.wait(m_mutex, [&]() VL_REQUIRES(m_mutex) {
|
2023-01-27 16:43:50 +01:00
|
|
|
return !m_queue.empty() || m_shutdown || m_stopRequested;
|
|
|
|
|
});
|
|
|
|
|
if (m_shutdown) return; // Terminate if requested
|
2023-06-06 01:40:36 +02:00
|
|
|
if (stopRequested()) { continue; }
|
2023-01-27 16:43:50 +01:00
|
|
|
// Get the job
|
|
|
|
|
UASSERT(!m_queue.empty(), "Job should be available");
|
|
|
|
|
|
2023-06-24 00:25:12 +02:00
|
|
|
job = std::move(m_queue.front());
|
2023-01-27 16:43:50 +01:00
|
|
|
m_queue.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute the job
|
|
|
|
|
job();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 01:40:36 +02:00
|
|
|
bool V3ThreadPool::waitIfStopRequested() VL_MT_SAFE VL_EXCLUDES(m_stoppedJobsMutex) {
|
2023-01-27 16:43:50 +01:00
|
|
|
if (!stopRequested()) return false;
|
2023-06-06 01:40:36 +02:00
|
|
|
V3LockGuard stoppedJobLock(m_stoppedJobsMutex);
|
2023-09-11 19:43:26 +02:00
|
|
|
waitForResumeRequest();
|
2023-01-27 16:43:50 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-11 19:43:26 +02:00
|
|
|
void V3ThreadPool::waitForResumeRequest() VL_REQUIRES(m_stoppedJobsMutex) {
|
2023-01-27 16:43:50 +01:00
|
|
|
++m_stoppedJobs;
|
|
|
|
|
m_stoppedJobsCV.notify_all();
|
2023-05-13 16:32:33 +02:00
|
|
|
m_stoppedJobsCV.wait(m_stoppedJobsMutex, [&]() VL_REQUIRES(m_stoppedJobsMutex) {
|
|
|
|
|
return !m_stopRequested.load();
|
|
|
|
|
});
|
2023-01-27 16:43:50 +01:00
|
|
|
--m_stoppedJobs;
|
|
|
|
|
m_stoppedJobsCV.notify_all();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-11 19:43:26 +02:00
|
|
|
void V3ThreadPool::stopOtherThreads() VL_MT_SAFE_EXCLUDES(m_mutex)
|
2023-04-11 13:23:24 +02:00
|
|
|
VL_REQUIRES(m_stoppedJobsMutex) {
|
2023-09-11 19:43:26 +02:00
|
|
|
m_stopRequested = true;
|
2023-01-27 16:43:50 +01:00
|
|
|
++m_stoppedJobs;
|
|
|
|
|
m_stoppedJobsCV.notify_all();
|
|
|
|
|
m_cv.notify_all();
|
2023-05-13 16:32:33 +02:00
|
|
|
m_stoppedJobsCV.wait(m_stoppedJobsMutex, [&]() VL_REQUIRES(m_stoppedJobsMutex) {
|
2023-01-27 16:43:50 +01:00
|
|
|
// count also the main thread
|
|
|
|
|
return m_stoppedJobs == (m_workers.size() + 1);
|
|
|
|
|
});
|
|
|
|
|
--m_stoppedJobs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void V3ThreadPool::selfTest() {
|
2023-04-11 13:23:24 +02:00
|
|
|
V3Mutex commonMutex;
|
2023-01-27 16:43:50 +01:00
|
|
|
int commonValue{0};
|
|
|
|
|
|
|
|
|
|
auto firstJob = [&](int sleep) -> void {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{sleep});
|
2023-06-24 00:25:12 +02:00
|
|
|
const V3ThreadPool::ScopedExclusiveAccess exclusiveAccess;
|
|
|
|
|
commonValue = 10;
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{sleep + 10});
|
|
|
|
|
UASSERT(commonValue == 10, "unexpected commonValue = " << commonValue);
|
2023-01-27 16:43:50 +01:00
|
|
|
};
|
|
|
|
|
auto secondJob = [&](int sleep) -> void {
|
2023-05-13 16:32:33 +02:00
|
|
|
commonMutex.lock();
|
|
|
|
|
commonMutex.unlock();
|
2023-01-27 16:43:50 +01:00
|
|
|
s().waitIfStopRequested();
|
2023-05-13 16:32:33 +02:00
|
|
|
V3LockGuard lock{commonMutex};
|
2023-01-27 16:43:50 +01:00
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{sleep});
|
|
|
|
|
commonValue = 1000;
|
|
|
|
|
};
|
|
|
|
|
auto thirdJob = [&](int sleep) -> void {
|
2023-05-13 16:32:33 +02:00
|
|
|
{
|
|
|
|
|
V3LockGuard lock{commonMutex};
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{sleep});
|
|
|
|
|
}
|
2023-01-27 16:43:50 +01:00
|
|
|
s().requestExclusiveAccess([&]() { firstJob(sleep); });
|
2023-05-13 16:32:33 +02:00
|
|
|
V3LockGuard lock{commonMutex};
|
2023-01-27 16:43:50 +01:00
|
|
|
commonValue = 1000;
|
|
|
|
|
};
|
|
|
|
|
std::list<std::future<void>> futures;
|
|
|
|
|
|
2023-06-24 00:25:12 +02:00
|
|
|
futures.push_back(s().enqueue(std::bind(firstJob, 100)));
|
|
|
|
|
futures.push_back(s().enqueue(std::bind(secondJob, 100)));
|
|
|
|
|
futures.push_back(s().enqueue(std::bind(firstJob, 100)));
|
|
|
|
|
futures.push_back(s().enqueue(std::bind(secondJob, 100)));
|
|
|
|
|
futures.push_back(s().enqueue(std::bind(secondJob, 200)));
|
|
|
|
|
futures.push_back(s().enqueue(std::bind(firstJob, 200)));
|
|
|
|
|
futures.push_back(s().enqueue(std::bind(firstJob, 300)));
|
2023-01-27 16:43:50 +01:00
|
|
|
while (!futures.empty()) {
|
|
|
|
|
s().waitForFuture(futures.front());
|
|
|
|
|
futures.pop_front();
|
|
|
|
|
}
|
2023-06-24 00:25:12 +02:00
|
|
|
futures.push_back(s().enqueue(std::bind(thirdJob, 100)));
|
|
|
|
|
futures.push_back(s().enqueue(std::bind(thirdJob, 100)));
|
2023-04-12 14:49:48 +02:00
|
|
|
V3ThreadPool::waitForFutures(futures);
|
2023-05-06 04:50:28 +02:00
|
|
|
|
2023-01-27 16:43:50 +01:00
|
|
|
s().waitIfStopRequested();
|
|
|
|
|
s().requestExclusiveAccess(std::bind(firstJob, 100));
|
2023-05-06 04:50:28 +02:00
|
|
|
|
2023-01-27 16:43:50 +01:00
|
|
|
auto forthJob = [&]() -> int { return 1234; };
|
|
|
|
|
std::list<std::future<int>> futuresInt;
|
2023-06-24 00:25:12 +02:00
|
|
|
futuresInt.push_back(s().enqueue(forthJob));
|
2023-04-12 14:49:48 +02:00
|
|
|
auto result = V3ThreadPool::waitForFutures(futuresInt);
|
|
|
|
|
UASSERT(result.back() == 1234, "unexpected future result = " << result.back());
|
2023-01-27 16:43:50 +01:00
|
|
|
}
|