Rewrote Task Scheduler

Better and faster implementation


Former-commit-id: d765415ca0576cd843480292e30c2d7bafa7f66a
This commit is contained in:
Lynix
2013-11-30 13:48:01 +01:00
parent 5eeb409484
commit 55aed0ec68
7 changed files with 287 additions and 149 deletions

View File

@@ -3,86 +3,23 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Core/TaskScheduler.hpp>
#include <Nazara/Core/ConditionVariable.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/HardwareInfo.hpp>
#include <Nazara/Core/LockGuard.hpp>
#include <Nazara/Core/Mutex.hpp>
#include <Nazara/Core/Thread.hpp>
#include <atomic>
#include <queue>
#include <vector>
#include <Nazara/Core/Debug.hpp>
///FIXME: Revoir tout ça
#if defined(NAZARA_PLATFORM_WINDOWS)
#include <Nazara/Core/Win32/TaskSchedulerImpl.hpp>
#elif defined(NAZARA_PLATFORM_POSIX)
#include <Nazara/Core/Posix/TaskSchedulerImpl.hpp>
#else
#error Lack of implementation: Task Scheduler
#endif
#include <Nazara/Core/Debug.hpp>
namespace
{
struct TaskSchedulerImpl
{
std::queue<NzFunctor*> tasks;
std::vector<NzThread> workers;
NzConditionVariable waiterConditionVariable;
NzConditionVariable workerConditionVariable;
NzMutex taskMutex;
NzMutex taskCountMutex;
NzMutex waiterConditionVariableMutex;
NzMutex workerConditionVariableMutex;
volatile bool running = true;
std::atomic<unsigned int> taskCount;
};
TaskSchedulerImpl* s_impl = nullptr;
std::vector<NzFunctor*> s_pendingWorks;
unsigned int s_workerCount = 0;
void WorkerFunc()
{
do
{
NzFunctor* task;
{
NzLockGuard lock(s_impl->taskMutex);
if (!s_impl->tasks.empty())
{
task = s_impl->tasks.front();
s_impl->tasks.pop();
}
else
task = nullptr;
}
// Avons-nous une tâche ?
if (task)
{
task->Run(); // Chouette ! Allons travailler gaiement
delete task; // Sans oublier de supprimer la tâche
s_impl->taskCountMutex.Lock();
#ifdef NAZARA_DEBUG
if (s_impl->taskCount == 0)
NazaraInternalError("Task count is already 0");
#endif
if (--s_impl->taskCount == 0)
{
// On peut signaler la fin du travail
s_impl->waiterConditionVariableMutex.Lock();
s_impl->waiterConditionVariable.Signal();
s_impl->waiterConditionVariableMutex.Unlock();
}
s_impl->taskCountMutex.Unlock();
}
else
{
// Nous attendons qu'une nouvelle tâche arrive
s_impl->workerConditionVariableMutex.Lock();
s_impl->workerConditionVariable.Wait(&s_impl->workerConditionVariableMutex);
s_impl->workerConditionVariableMutex.Unlock();
}
}
while (s_impl->running);
}
}
unsigned int NzTaskScheduler::GetWorkerCount()
@@ -92,102 +29,47 @@ unsigned int NzTaskScheduler::GetWorkerCount()
bool NzTaskScheduler::Initialize()
{
if (s_impl)
return true; // Déjà initialisé
return NzTaskSchedulerImpl::Initialize(GetWorkerCount());
}
s_impl = new TaskSchedulerImpl;
unsigned int workerCount = GetWorkerCount();
s_impl->workers.resize(workerCount);
for (unsigned int i = 0; i < workerCount; ++i)
s_impl->workers[i] = NzThread(WorkerFunc);
return true;
void NzTaskScheduler::Run()
{
NzTaskSchedulerImpl::Run(&s_pendingWorks[0], s_pendingWorks.size());
s_pendingWorks.clear();
}
void NzTaskScheduler::SetWorkerCount(unsigned int workerCount)
{
s_workerCount = workerCount;
if (s_impl)
{
unsigned int newWorkerCount = GetWorkerCount();
unsigned int oldWorkerCount = s_impl->workers.size();
s_impl->workers.resize(newWorkerCount);
if (newWorkerCount > oldWorkerCount)
{
for (unsigned int i = oldWorkerCount-1; i < newWorkerCount; ++i)
s_impl->workers[i] = NzThread(WorkerFunc);
}
}
}
void NzTaskScheduler::Uninitialize()
{
if (s_impl)
{
s_impl->running = false;
// S'il reste des tâches en cours, on les libère
{
NzLockGuard lock(s_impl->taskMutex);
while (!s_impl->tasks.empty())
{
delete s_impl->tasks.front();
s_impl->tasks.pop();
}
}
// Ensuite on réveille les threads pour qu'ils s'arrêtent d'eux-même
s_impl->workerConditionVariableMutex.Lock();
s_impl->workerConditionVariable.SignalAll();
s_impl->workerConditionVariableMutex.Unlock();
for (NzThread& thread : s_impl->workers)
thread.Join();
delete s_impl;
s_impl = nullptr;
}
}
void NzTaskScheduler::WaitForTasks()
{
#ifdef NAZARA_CORE_SAFE
if (!s_impl)
if (NzTaskSchedulerImpl::IsInitialized())
{
NazaraError("Task scheduler is not initialized");
NazaraError("Worker count cannot be set while initialized");
return;
}
#endif
// Tout d'abord, il y a-t-il des tâches en attente ?
if (s_impl->tasks.empty())
return;
s_workerCount = workerCount;
}
s_impl->taskCount = s_impl->tasks.size();
void NzTaskScheduler::Uninitialize()
{
NzTaskSchedulerImpl::Uninitialize();
}
// On verrouille d'abord la mutex entourant le signal (Pour ne pas perdre le signal en chemin)
s_impl->waiterConditionVariableMutex.Lock();
// Et ensuite seulement on réveille les worker
s_impl->workerConditionVariableMutex.Lock();
s_impl->workerConditionVariable.SignalAll();
s_impl->workerConditionVariableMutex.Unlock();
s_impl->waiterConditionVariable.Wait(&s_impl->waiterConditionVariableMutex);
s_impl->waiterConditionVariableMutex.Unlock();
void NzTaskScheduler::WaitForTasks()
{
NzTaskSchedulerImpl::WaitForTasks();
}
void NzTaskScheduler::AddTaskFunctor(NzFunctor* taskFunctor)
{
#ifdef NAZARA_CORE_SAFE
if (!s_impl)
if (!NzTaskSchedulerImpl::IsInitialized())
{
NazaraError("Task scheduler is not initialized");
return;
}
#endif
s_impl->tasks.push(taskFunctor);
s_pendingWorks.push_back(taskFunctor);
}