Use C++11 threads, mutexes and condition variables

fix-squashed-planets
AbigailBuccaneer 2018-04-19 20:29:06 +01:00
parent e964d3e48c
commit 2a003a27b1
11 changed files with 33 additions and 395 deletions

View File

@ -150,10 +150,6 @@ set(BASE_SOURCES
common/singleton.h common/singleton.h
common/stringutils.cpp common/stringutils.cpp
common/stringutils.h common/stringutils.h
common/thread/resource_owning_thread.h
common/thread/sdl_cond_wrapper.h
common/thread/sdl_mutex_wrapper.h
common/thread/thread.h
common/thread/worker_thread.h common/thread/worker_thread.h
graphics/core/color.cpp graphics/core/color.cpp
graphics/core/color.h graphics/core/color.h

View File

@ -36,8 +36,6 @@
#include "common/system/system.h" #include "common/system/system.h"
#include "common/thread/thread.h"
#include "graphics/core/nulldevice.h" #include "graphics/core/nulldevice.h"
#include "graphics/opengl/glutil.h" #include "graphics/opengl/glutil.h"
@ -60,6 +58,7 @@
#include <libintl.h> #include <libintl.h>
#include <getopt.h> #include <getopt.h>
#include <localename.h> #include <localename.h>
#include <thread>
char CApplication::m_languageLocale[] = { 0 }; char CApplication::m_languageLocale[] = { 0 };
@ -671,8 +670,7 @@ bool CApplication::Create()
// Create the robot application. // Create the robot application.
m_controller = MakeUnique<CController>(); m_controller = MakeUnique<CController>();
CThread musicLoadThread([this]() std::thread{[this]() {
{
GetLogger()->Debug("Cache sounds...\n"); GetLogger()->Debug("Cache sounds...\n");
SystemTimeStamp* musicLoadStart = m_systemUtils->CreateTimeStamp(); SystemTimeStamp* musicLoadStart = m_systemUtils->CreateTimeStamp();
m_systemUtils->GetCurrentTimeStamp(musicLoadStart); m_systemUtils->GetCurrentTimeStamp(musicLoadStart);
@ -683,9 +681,7 @@ bool CApplication::Create()
m_systemUtils->GetCurrentTimeStamp(musicLoadEnd); m_systemUtils->GetCurrentTimeStamp(musicLoadEnd);
float musicLoadTime = m_systemUtils->TimeStampDiff(musicLoadStart, musicLoadEnd, STU_MSEC); float musicLoadTime = m_systemUtils->TimeStampDiff(musicLoadStart, musicLoadEnd, STU_MSEC);
GetLogger()->Debug("Sound loading took %.2f ms\n", musicLoadTime); GetLogger()->Debug("Sound loading took %.2f ms\n", musicLoadTime);
}, }}.detach();
"Sound loading thread");
musicLoadThread.Start();
if (m_runSceneCategory == LevelCategory::Max) if (m_runSceneCategory == LevelCategory::Max)
m_controller->StartApp(); m_controller->StartApp();

View File

@ -563,8 +563,7 @@ std::string ParseEventType(EventType eventType)
CEventQueue::CEventQueue() CEventQueue::CEventQueue()
: m_mutex{}, : m_fifo(),
m_fifo(),
m_head{0}, m_head{0},
m_tail{0}, m_tail{0},
m_total{0} m_total{0}
@ -582,15 +581,13 @@ bool CEventQueue::IsEmpty()
Else, adds the event to the queue and returns \c true. */ Else, adds the event to the queue and returns \c true. */
bool CEventQueue::AddEvent(Event&& event) bool CEventQueue::AddEvent(Event&& event)
{ {
bool result{}; std::lock_guard<std::mutex> lock{m_mutex};
SDL_LockMutex(*m_mutex);
if (m_total >= MAX_EVENT_QUEUE) if (m_total >= MAX_EVENT_QUEUE)
{ {
GetLogger()->Warn("Event queue flood!\n"); GetLogger()->Warn("Event queue flood!\n");
result = false; return false;
} }
else else
{ {
@ -601,19 +598,15 @@ bool CEventQueue::AddEvent(Event&& event)
m_total++; m_total++;
result = true; return true;
} }
SDL_UnlockMutex(*m_mutex);
return result;
} }
Event CEventQueue::GetEvent() Event CEventQueue::GetEvent()
{ {
Event event; Event event;
SDL_LockMutex(*m_mutex); std::lock_guard<std::mutex> lock{m_mutex};
if (m_head == m_tail) if (m_head == m_tail)
{ {
@ -630,7 +623,5 @@ Event CEventQueue::GetEvent()
} }
SDL_UnlockMutex(*m_mutex);
return event; return event;
} }

View File

@ -27,12 +27,11 @@
#include "common/key.h" #include "common/key.h"
#include "common/make_unique.h" #include "common/make_unique.h"
#include "common/thread/sdl_mutex_wrapper.h"
#include "math/point.h" #include "math/point.h"
#include "math/vector.h" #include "math/vector.h"
#include <memory> #include <memory>
#include <mutex>
/** /**
\enum EventType \enum EventType
@ -888,7 +887,7 @@ public:
Event GetEvent(); Event GetEvent();
protected: protected:
CSDLMutexWrapper m_mutex; std::mutex m_mutex;
Event m_fifo[MAX_EVENT_QUEUE]; Event m_fifo[MAX_EVENT_QUEUE];
int m_head; int m_head;
int m_tail; int m_tail;

View File

@ -1,133 +0,0 @@
/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2018, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsitec.ch; http://colobot.info; http://github.com/colobot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://gnu.org/licenses
*/
#pragma once
#include "common/thread/sdl_cond_wrapper.h"
#include "common/thread/sdl_mutex_wrapper.h"
#include <SDL_thread.h>
#include <memory>
#include <string>
/**
* \class CResourceOwningThread
* \brief Wrapper around SDL thread allowing passing of resources in safe manner
*
* This class is a workaround for passing ownership of resources in a safe
* manner to newly created threads. It takes a pointer to a function to call
* in new thread and a unique_ptr to resource which is to be passed to the new thread.
*
* This is how it works:
* - in main thread: create a new thread passing to it a special temporary context,
* - in main thread: wait for synchronization signal that the ownership was passed,
* - in new thread: acquire the resource from the context
* - in new thread: signal back to main thread that the resource was acquired,
* - in main thread: clean up temporary context and exit
* - in new thread: run the specified function with the acquired resource.
*
* It's a bit complicated, but that's the safe (thread-safe and exception-safe)
* way of doing this.
*/
template<typename Resource>
class CResourceOwningThread
{
public:
using ResourceUPtr = std::unique_ptr<Resource>;
using ThreadFunctionPtr = void(*)(ResourceUPtr);
CResourceOwningThread(ThreadFunctionPtr threadFunction, ResourceUPtr resource, std::string name = "")
: m_threadFunction(threadFunction),
m_resource(std::move(resource)),
m_name(name)
{}
~CResourceOwningThread()
{
SDL_DetachThread(m_thread);
}
void Start()
{
CSDLMutexWrapper mutex;
CSDLCondWrapper cond;
bool condition = false;
ThreadData data;
data.resource = std::move(m_resource);
data.threadFunction = m_threadFunction;
data.mutex = &mutex;
data.cond = &cond;
data.condition = &condition;
SDL_LockMutex(*mutex);
m_thread = SDL_CreateThread(Run, !m_name.empty() ? m_name.c_str() : nullptr, reinterpret_cast<void*>(&data));
while (!condition)
{
SDL_CondWait(*cond, *mutex);
}
SDL_UnlockMutex(*mutex);
}
void Join()
{
if (m_thread == nullptr) return;
SDL_WaitThread(m_thread, nullptr);
m_thread = nullptr;
}
private:
static int Run(void* data)
{
ThreadFunctionPtr threadFunction = nullptr;
ResourceUPtr resource;
ThreadData* threadData = reinterpret_cast<ThreadData*>(data);
SDL_LockMutex(**threadData->mutex);
threadFunction = threadData->threadFunction;
resource = std::move(threadData->resource);
*threadData->condition = true;
SDL_CondSignal(**threadData->cond);
SDL_UnlockMutex(**threadData->mutex);
threadFunction(std::move(resource));
return 0;
}
private:
struct ThreadData
{
ResourceUPtr resource;
CSDLMutexWrapper* mutex = nullptr;
CSDLCondWrapper* cond = nullptr;
bool* condition = nullptr;
ThreadFunctionPtr threadFunction = nullptr;
};
ThreadFunctionPtr m_threadFunction;
ResourceUPtr m_resource;
std::string m_name;
SDL_Thread* m_thread = nullptr;
};

View File

@ -1,62 +0,0 @@
/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2018, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsitec.ch; http://colobot.info; http://github.com/colobot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://gnu.org/licenses
*/
#pragma once
#include "common/thread/sdl_mutex_wrapper.h"
#include <SDL_thread.h>
/**
* \class CSDLCondWrapper
* \brief Wrapper for safe creation/deletion of SDL_cond
*/
class CSDLCondWrapper
{
public:
CSDLCondWrapper()
: m_cond(SDL_CreateCond())
{}
~CSDLCondWrapper()
{
SDL_DestroyCond(m_cond);
}
CSDLCondWrapper(const CSDLCondWrapper&) = delete;
CSDLCondWrapper& operator=(const CSDLCondWrapper&) = delete;
SDL_cond* operator*()
{
return m_cond;
}
void Signal()
{
SDL_CondSignal(m_cond);
}
void Wait(SDL_mutex* mutex)
{
SDL_CondWait(m_cond, mutex);
}
private:
SDL_cond* m_cond;
};

View File

@ -1,60 +0,0 @@
/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2018, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsitec.ch; http://colobot.info; http://github.com/colobot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://gnu.org/licenses
*/
#pragma once
#include <SDL_thread.h>
/**
* \class CSDLMutexWrapper
* \brief Wrapper for safe creation/deletion of SDL_mutex
*/
class CSDLMutexWrapper
{
public:
CSDLMutexWrapper()
: m_mutex(SDL_CreateMutex())
{}
~CSDLMutexWrapper()
{
SDL_DestroyMutex(m_mutex);
}
CSDLMutexWrapper(const CSDLMutexWrapper&) = delete;
CSDLMutexWrapper& operator=(const CSDLMutexWrapper&) = delete;
SDL_mutex* operator*()
{
return m_mutex;
}
void Lock()
{
SDL_LockMutex(m_mutex);
}
void Unlock()
{
SDL_UnlockMutex(m_mutex);
}
private:
SDL_mutex* m_mutex;
};

View File

@ -1,77 +0,0 @@
/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2018, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsitec.ch; http://colobot.info; http://github.com/colobot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://gnu.org/licenses
*/
#pragma once
#include "common/make_unique.h"
#include "common/thread/resource_owning_thread.h"
#include <functional>
#include <string>
#include <memory>
/**
* \class CThread
* \brief Wrapper for using SDL_thread with std::function
*/
class CThread
{
public:
using ThreadFunctionPtr = std::function<void()>;
private:
struct ThreadData
{
ThreadFunctionPtr func;
};
public:
CThread(ThreadFunctionPtr func, std::string name = "")
: m_func(std::move(func))
, m_name(name)
{}
void Start()
{
std::unique_ptr<ThreadData> data = MakeUnique<ThreadData>();
data->func = m_func;
m_thread = MakeUnique<CResourceOwningThread<ThreadData>>(Run, std::move(data), m_name);
m_thread->Start();
}
void Join()
{
if (!m_thread) return;
m_thread->Join();
}
CThread(const CThread&) = delete;
CThread& operator=(const CThread&) = delete;
private:
static void Run(std::unique_ptr<ThreadData> data)
{
data->func();
}
std::unique_ptr<CResourceOwningThread<ThreadData>> m_thread;
ThreadFunctionPtr m_func;
std::string m_name;
};

View File

@ -21,13 +21,12 @@
#include "common/make_unique.h" #include "common/make_unique.h"
#include "common/thread/sdl_cond_wrapper.h" #include <condition_variable>
#include "common/thread/sdl_mutex_wrapper.h"
#include "common/thread/thread.h"
#include <functional> #include <functional>
#include <string> #include <mutex>
#include <queue> #include <queue>
#include <string>
#include <thread>
/** /**
* \class CWorkerThread * \class CWorkerThread
@ -39,27 +38,23 @@ public:
using ThreadFunctionPtr = std::function<void()>; using ThreadFunctionPtr = std::function<void()>;
public: public:
CWorkerThread(std::string name = "") CWorkerThread() : m_thread{&CWorkerThread::Run, this} {}
: m_thread(std::bind(&CWorkerThread::Run, this), name)
{
m_thread.Start();
}
~CWorkerThread() ~CWorkerThread()
{ {
m_mutex.Lock(); {
m_running = false; std::lock_guard<std::mutex> lock{m_mutex};
m_cond.Signal(); m_running = false;
m_mutex.Unlock(); m_cond.notify_one();
m_thread.Join(); }
m_thread.join();
} }
void Start(ThreadFunctionPtr func) void Start(ThreadFunctionPtr&& func)
{ {
m_mutex.Lock(); std::lock_guard<std::mutex> lock{m_mutex};
m_queue.push(func); m_queue.push(func);
m_cond.Signal(); m_cond.notify_one();
m_mutex.Unlock();
} }
CWorkerThread(const CWorkerThread&) = delete; CWorkerThread(const CWorkerThread&) = delete;
@ -68,25 +63,21 @@ public:
private: private:
void Run() void Run()
{ {
m_mutex.Lock(); auto lock = std::unique_lock<std::mutex>(m_mutex);
while (true) while (true)
{ {
while (m_queue.empty() && m_running) m_cond.wait(lock, [&]() { return !m_running || !m_queue.empty(); });
{
m_cond.Wait(*m_mutex);
}
if (!m_running) break; if (!m_running) break;
ThreadFunctionPtr func = m_queue.front(); ThreadFunctionPtr func = std::move(m_queue.front());
m_queue.pop(); m_queue.pop();
func(); func();
} }
m_mutex.Unlock();
} }
CThread m_thread; std::thread m_thread;
CSDLMutexWrapper m_mutex; std::mutex m_mutex;
CSDLCondWrapper m_cond; std::condition_variable m_cond;
bool m_running = true; bool m_running = true;
std::queue<ThreadFunctionPtr> m_queue; std::queue<ThreadFunctionPtr> m_queue;
}; };

View File

@ -32,8 +32,6 @@
#include "common/system/system.h" #include "common/system/system.h"
#include "common/thread/resource_owning_thread.h"
#include "graphics/core/device.h" #include "graphics/core/device.h"
#include "graphics/core/framebuffer.h" #include "graphics/core/framebuffer.h"
@ -63,6 +61,7 @@
#include <iomanip> #include <iomanip>
#include <SDL_surface.h> #include <SDL_surface.h>
#include <SDL_thread.h> #include <SDL_thread.h>
#include <thread>
// Graphics module namespace // Graphics module namespace
namespace Gfx namespace Gfx
@ -500,8 +499,7 @@ void CEngine::WriteScreenShot(const std::string& fileName)
data->fileName = fileName; data->fileName = fileName;
CResourceOwningThread<WriteScreenShotData> thread(CEngine::WriteScreenShotThread, std::move(data), "WriteScreenShot thread"); std::thread{&CEngine::WriteScreenShotThread, std::move(data)}.detach();
thread.Start();
} }
void CEngine::WriteScreenShotThread(std::unique_ptr<WriteScreenShotData> data) void CEngine::WriteScreenShotThread(std::unique_ptr<WriteScreenShotData> data)

View File

@ -32,8 +32,7 @@ CALSound::CALSound()
m_musicVolume(1.0f), m_musicVolume(1.0f),
m_channelsLimit(2048), m_channelsLimit(2048),
m_device{}, m_device{},
m_context{}, m_context{}
m_thread("Music loading thread")
{ {
} }