mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-07-01 21:25:11 +00:00
221
global/scheduler/scheduler.cpp
Normal file
221
global/scheduler/scheduler.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#include "scheduler.h"
|
||||
#include <cassert>
|
||||
|
||||
CTaskScheduler::CTaskScheduler(size_t nMinIdle /*= 4*/, size_t nMaxBusy /*= 32*/) :
|
||||
m_nMinIdle(nMinIdle), m_nMaxBusy(nMaxBusy)
|
||||
{
|
||||
// Check min/max values
|
||||
assert(nMinIdle > 0);
|
||||
if (!m_nMinIdle) m_nMinIdle = 1;
|
||||
assert(nMinIdle > 0);
|
||||
if (!m_nMaxBusy) m_nMaxBusy = 1;
|
||||
|
||||
// Start the minimal required idle threads
|
||||
for (size_t n = 0; n < nMinIdle; n++)
|
||||
m_queueIdleThreads.push(
|
||||
m_lstThreads.insert(m_lstThreads.end(), std::make_shared<CThread>()));
|
||||
m_nMaxThreads = m_lstThreads.size();
|
||||
}
|
||||
|
||||
CTaskScheduler::~CTaskScheduler()
|
||||
{
|
||||
WaitForExecution();
|
||||
}
|
||||
|
||||
bool CTaskScheduler::Schedule(std::function<void()> fnTask, hlpr::flags<EScheduleFlags> flags /*= 0*/)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
|
||||
// Get a thread - either from the idle queue or new.
|
||||
std::list<std::shared_ptr<CThread>>::iterator itThread = m_lstThreads.end();
|
||||
if (m_queueIdleThreads.empty())
|
||||
{
|
||||
// No thread is available. Allowed to make one?
|
||||
if (m_lstThreads.size() < m_nMaxBusy)
|
||||
itThread = m_lstThreads.insert(m_lstThreads.end(), std::make_shared<CThread>());
|
||||
if (m_lstThreads.size() > m_nMaxThreads)
|
||||
m_nMaxThreads = m_lstThreads.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
itThread = m_queueIdleThreads.front();
|
||||
m_queueIdleThreads.pop();
|
||||
}
|
||||
|
||||
// Is there a valid thread? The schedule the task. Otherwise queue the task.
|
||||
if (itThread != m_lstThreads.end())
|
||||
{
|
||||
lock.unlock();
|
||||
Execute(itThread, fnTask);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queuing allowed?
|
||||
if (flags & EScheduleFlags::no_queue) return false; // No scheduling possible.
|
||||
|
||||
// Add the task to the queue
|
||||
if (flags & EScheduleFlags::priority)
|
||||
m_dequeTasks.push_front(fnTask);
|
||||
else
|
||||
m_dequeTasks.push_back(fnTask);
|
||||
}
|
||||
|
||||
// Successful scheduled.
|
||||
return true;
|
||||
}
|
||||
|
||||
void CTaskScheduler::WaitForExecution()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
|
||||
// Temporarily reduce the idle level to zero.
|
||||
size_t nMinIdleTemp = m_nMinIdle;
|
||||
m_nMinIdle = 0;
|
||||
|
||||
// Wait until the thread list is empty.
|
||||
while (!m_lstThreads.empty())
|
||||
{
|
||||
// Erase all idle threads.
|
||||
while (!m_queueIdleThreads.empty())
|
||||
{
|
||||
// False positive of CppCheck: content of m_queueIdleThreads represents the iterator of m_lstThreads
|
||||
// cppcheck-suppress mismatchingContainerIterator
|
||||
m_lstThreads.erase(m_queueIdleThreads.front());
|
||||
m_queueIdleThreads.pop();
|
||||
}
|
||||
|
||||
// Allow execution threads to finalize its processing
|
||||
lock.unlock();
|
||||
|
||||
// Wait shortly
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
// Check again
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
// Restore the idle thread amount
|
||||
m_nMinIdle = nMinIdleTemp;
|
||||
}
|
||||
|
||||
size_t CTaskScheduler::GetThreadCount() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
return m_lstThreads.size();
|
||||
}
|
||||
|
||||
size_t CTaskScheduler::GetMaxThreadCount() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
return m_nMaxThreads;
|
||||
}
|
||||
|
||||
size_t CTaskScheduler::GetBusyThreadCount() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
return m_lstThreads.size() - m_queueIdleThreads.size();
|
||||
}
|
||||
|
||||
size_t CTaskScheduler::GetIdleThreadCount() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
return m_queueIdleThreads.size();
|
||||
}
|
||||
|
||||
void CTaskScheduler::Execute(std::list<std::shared_ptr<CThread>>::iterator itThread, std::function<void()> fnTask)
|
||||
{
|
||||
std::shared_ptr<CThread>& rptrThread = *itThread;
|
||||
|
||||
// Call execute.
|
||||
rptrThread->Execute([this, fnTask, itThread]()
|
||||
{
|
||||
// Start with the supplied task.
|
||||
std::function<void()> fnTaskLocal = fnTask;
|
||||
|
||||
// Execute tasks for as long as there are any
|
||||
do
|
||||
{
|
||||
fnTaskLocal();
|
||||
|
||||
// Any other task?
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
if (m_dequeTasks.empty()) break;
|
||||
fnTaskLocal = std::move(m_dequeTasks.front());
|
||||
m_dequeTasks.pop_front();
|
||||
|
||||
} while (true);
|
||||
|
||||
|
||||
// Prepare for adding this thread to the idle queue. Remove any redundant threads.
|
||||
// Attention: it is not possible to terminate this thread, since it still runs the thread func and relies on member
|
||||
// variables (which would otherwise be cleared). It is, however, possible to remove all other threads.
|
||||
std::unique_lock<std::mutex> lock2(m_mtxQueueAccess);
|
||||
while (!m_queueIdleThreads.empty() && m_queueIdleThreads.size() >= m_nMinIdle)
|
||||
{
|
||||
// False positive of CppCheck: content of m_queueIdleThreads represents the iterator of m_lstThreads
|
||||
// cppcheck-suppress mismatchingContainerIterator
|
||||
m_lstThreads.erase(m_queueIdleThreads.front());
|
||||
m_queueIdleThreads.pop();
|
||||
}
|
||||
|
||||
// Add this thread to the idle queue.
|
||||
m_queueIdleThreads.push(itThread);
|
||||
});
|
||||
}
|
||||
|
||||
CTaskScheduler::CThread::CThread()
|
||||
{
|
||||
// Wait for the start
|
||||
std::unique_lock<std::mutex> lockStart(m_mtxSyncStart);
|
||||
m_thread = std::thread(&CThread::ThreadFunc, this);
|
||||
while (!m_bStarted)
|
||||
m_cvStarted.wait_for(lockStart, std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
CTaskScheduler::CThread::~CThread()
|
||||
{
|
||||
// Lock to prevent execution still to take place. Then shutdown.
|
||||
std::unique_lock<std::mutex> lock(m_mtxSyncExecute);
|
||||
m_bShutdown = true;
|
||||
m_cvExecute.notify_all();
|
||||
lock.unlock();
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void CTaskScheduler::CThread::Execute(std::function<void()> fnTask)
|
||||
{
|
||||
// Assign the task and notify for execution.
|
||||
std::unique_lock<std::mutex> lock(m_mtxSyncExecute);
|
||||
m_fnTask = fnTask;
|
||||
m_cvExecute.notify_all();
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
void CTaskScheduler::CThread::ThreadFunc()
|
||||
{
|
||||
// Thread has started (needed, since the condition variable doesn't keep its state).
|
||||
m_bStarted = true;
|
||||
|
||||
// Notify the thread has started
|
||||
std::unique_lock<std::mutex> lockStart(m_mtxSyncStart);
|
||||
m_cvStarted.notify_all();
|
||||
lockStart.unlock();
|
||||
|
||||
// Notify the thread has executed
|
||||
std::unique_lock<std::mutex> lockExecute(m_mtxSyncExecute);
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
// Wait for an execution task to take place.
|
||||
m_cvExecute.wait_for(lockExecute, std::chrono::milliseconds(10));
|
||||
|
||||
// In case there is no task scheduled.
|
||||
if (!m_fnTask) continue;
|
||||
|
||||
// Execute the task
|
||||
m_fnTask();
|
||||
|
||||
// Task is done, clear the task function
|
||||
m_fnTask = {};
|
||||
}
|
||||
}
|
||||
143
global/scheduler/scheduler.h
Normal file
143
global/scheduler/scheduler.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#ifndef THREAD_POOL_H
|
||||
#define THREAD_POOL_H
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include "../flags.h"
|
||||
|
||||
/**
|
||||
* @brief Job scheduler that uses a dynamic threadpool to schedule the task.
|
||||
*/
|
||||
class CTaskScheduler
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] nMinIdle The amount of idle threads that should stay present if there is nothing to process at the moment.
|
||||
* @param[in] nMaxBusy The maximum amount of threads that is used for processing.
|
||||
*/
|
||||
CTaskScheduler(size_t nMinIdle = 4, size_t nMaxBusy = 32);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CTaskScheduler();
|
||||
|
||||
/**
|
||||
* @brief Schedule flags to influence the scheduling.
|
||||
*/
|
||||
enum class EScheduleFlags
|
||||
{
|
||||
normal = 0x0, ///< If thread level threshold has been reached, queue the call at the end of the queue.
|
||||
priority = 0x1, ///< If thread level threshold has been reached, insert the call at the begin of the queue.
|
||||
no_queue = 0x2, ///< If thread level threshold has been reached, fail the schedule call.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Schedule the asynchronous execution of the task.
|
||||
* @details If an idle thread is available, the thread will execute the task. If no thread is available and the maximum thread
|
||||
* level hasn't been reached, a new thread will be started that schedules the task. If the maximum thread level has been
|
||||
* exceeded the task will be placed in the task list based on its priority and allowance. If queue is not allowed (set by the
|
||||
* no_queue flag), the scheduling will fail.
|
||||
* @param[in] fnTask The task to schedule.
|
||||
* @param[in] flags Zero or more flags to use for scheduling the task.
|
||||
* @return Returns whether the scheduling was successful or not.
|
||||
*/
|
||||
bool Schedule(std::function<void()> fnTask, hlpr::flags<EScheduleFlags> flags = EScheduleFlags::normal);
|
||||
|
||||
/**
|
||||
* @brief Wait until the execution of all threads has been finalized. This will also remove all idle threads.
|
||||
* @attention Do not call from a task function - that will cause a deadlock.
|
||||
*/
|
||||
void WaitForExecution();
|
||||
|
||||
/**
|
||||
* @brief Get the current amount of threads (idle + processing).
|
||||
* @return The amount of threads.
|
||||
*/
|
||||
size_t GetThreadCount() const;
|
||||
|
||||
/**
|
||||
* @brief Get the maximum amount of threads that were processing at one time.
|
||||
* @return The amount of threads.
|
||||
*/
|
||||
size_t GetMaxThreadCount() const;
|
||||
|
||||
/**
|
||||
* @brief Get the current amount of processing threads.
|
||||
* @return The amount of threads.
|
||||
*/
|
||||
size_t GetBusyThreadCount() const;
|
||||
|
||||
/**
|
||||
* @brief Get the current amount of idle threads.
|
||||
* @return The amount of threads.
|
||||
*/
|
||||
size_t GetIdleThreadCount() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Helper class for the thread scheduling
|
||||
*/
|
||||
struct CThread
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor starting the thread.
|
||||
*/
|
||||
CThread();
|
||||
|
||||
/**
|
||||
* @brief Destructor stopping the thread.
|
||||
*/
|
||||
~CThread();
|
||||
|
||||
/**
|
||||
* @brief Schedule an execution.
|
||||
* @param[in] fnTask The execution task.
|
||||
*/
|
||||
void Execute(std::function<void()> fnTask);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief The thread function to execute.
|
||||
*/
|
||||
void ThreadFunc();
|
||||
|
||||
std::thread m_thread; ///< The thread that executes the tasks.
|
||||
bool m_bShutdown = false; ///< Set when the thread should terminate.
|
||||
bool m_bStarted = false; ///< Set when the thread has started.
|
||||
std::function<void()> m_fnTask; ///< The task to execute (will be updated with new tasks before execution).
|
||||
std::mutex m_mtxSyncStart; ///< The startup synchronization mutex.
|
||||
std::condition_variable m_cvStarted; ///< Triggered by the thread to indicate that it has started.
|
||||
std::mutex m_mtxSyncExecute; ///< The execute synchronization mutex.
|
||||
std::condition_variable m_cvExecute; ///< Triggers the thread to indicate that there is a task to execute or
|
||||
///< shutdown has been requested.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Execute a task using the provided thread. After the execution, other tasks from the task list will be executed as
|
||||
* well. If after all executions are finalized and if there are enough idle threads, removes the thread from
|
||||
* the list. Otherwise adds the thread to the idle queue.
|
||||
* @param[in] itThread The thread to use for the execution.
|
||||
* @param[in] fnTask The task to execute.
|
||||
*/
|
||||
void Execute(std::list<std::shared_ptr<CThread>>::iterator itThread, std::function<void()> fnTask);
|
||||
|
||||
size_t m_nMinIdle = 4; ///< The minimal required amount of idle threads
|
||||
size_t m_nMaxBusy = 32; ///< The maximum allowed amount of busy threads
|
||||
size_t m_nMaxThreads = 0; ///< The maximum amount threads at the same time.
|
||||
mutable std::mutex m_mtxQueueAccess; ///< Sync access to queue, list and double-ended-queue.
|
||||
std::queue<std::list<std::shared_ptr<CThread>>::iterator> m_queueIdleThreads; ///< Idle thread queue.
|
||||
std::list<std::shared_ptr<CThread>> m_lstThreads; ///< List with all threads.
|
||||
std::deque<std::function<void()>> m_dequeTasks; ///< Double ended task queue.
|
||||
};
|
||||
|
||||
#endif // !defined THREAD_POOL_H
|
||||
Reference in New Issue
Block a user