Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
SFML Game Development.pdf
Скачиваний:
209
Добавлен:
28.03.2016
Размер:
4.19 Mб
Скачать

Chapter 5

Thread

A thread is essentially a function that runs in another branch of execution. You know the mandatory main() function, right? That is the entry point of every application, when it is called by the operating system, it creates one thread: the main one. You can visualize it as a sequential stream of commands that flows until the main() function is over and exits the program.

We can conclude from this that one thread is one function call. The same way main() is a thread function, there can be others. So, if inside the main function, we spawn a new thread by "calling" its function, the only difference between that and a normal call is that the program doesn't wait until that function ends, but rather continues execution while another stream of commands starts running in parallel.

This is the essence of multi-threading and there isn't a lot to add to it. To do this branching in execution, SFML provides sf::Thread. This class implies that you have to link a sf::Thread object to a function on its constructor, such as

sf::Thread(&myFunc) for a global function or sf::Thread(&MyClass::myFunc, myClassObject) for a member function of a class.

When you call sf::Thread::launch(), all it does is to call the linked function in a separate thread.

Once that function returns, the thread is also shut down automatically. Destroying the sf::Thread object while its thread is still running results in an abrupt termination of the thread.

Seems easy, right? Well, there are some more things to have in consideration. Using threads is very straightforward but there are concerns we must always have in mind when working with them.

Concurrency

When two threads are running in parallel, everything will go smoothly if they don't touch the same data at the same time. Anyway, it is very normal that two or more threads want to read/write to the same variables, if not for more, to communicate between them.

It probably goes without saying that if the processor is reading and writing to the same memory address at the same time, we are going to have a nice crash or an undefined behavior in our program, which can be troublesome to debug and correct.

But then, how to guarantee that multiple threads operate on shared data at turns, with proper synchronization? This is a very delicate topic that must not be taken lightly; however, there are mechanisms in SFML that help us achieve this!

[ 133 ]

www.it-ebooks.info

Diverting the Game Flow – State Stack

Here, we introduce you to sf::Mutex and sf::Lock. These are incredible tools to protect shared data when working with multi-threading. We won't be explaining in depth how they work internally, but rather try to apply it directly and understand it by example, by looking at the ParallelTask class implementation.

Task implementation

The code for this implementation is as follows:

class ParallelTask

 

{

 

public:

 

 

ParallelTask();

void

execute();

bool

isFinished();

float

getCompletion();

private:

 

void

runTask();

private:

 

sf::Thread

mThread;

bool

mFinished;

sf::Clock

mElapsedTime;

sf::Mutex

mMutex;

};

 

By taking a look at the ParallelTask declaration, you can see that the API used by LoadingState exposed. Besides that, you can see the runTask() function, which is the actual thread function that is launched in parallel execution.

Then, in its members, we can see the expected sf::Thread, the sf::Clock to count the elapsed time, and a Boolean variable to check if the task is done yet or not. The sf::Mutex object is meant to protect both the mClock and mFinished variables from concurrent access, as you will see in the following sample:

bool ParallelTask::isFinished()

{

sf::Lock lock(mMutex); return mFinished;

}

[ 134 ]

www.it-ebooks.info

Chapter 5

The first line in the function's body is what we call locking variables. If you noticed,

isFinished() is called by LoadingState::update(), which is the main thread while mFinished can be changed by the task thread as well, immediately when it finishes. This could randomly cause a simultaneous read and write by two threads, which would result in bad luck for us as programmers. We say randomly because there is nothing controlling the synchronization of the two threads, the simultaneous access can either happen or not.

By locking the sf::Mutex object before touching sensitive data, we ensure that if a thread tries to lock an already locked resource, it will wait until the other thread unlocks it. This creates synchronization between threads because they will wait in line to access shared data one at a time.

Because sf::Lock is a RAII compliant class, as soon as it goes out of scope and is destructed, the sf::Mutex object automatically unlocks.

To finalize, here is the actual thread function of our task:

void ParallelTask::runTask()

{

// Dummy task - stall 10 seconds bool ended = false;

while (!ended)

{

sf::Lock lock(mMutex); // Protect the clock

if (mElapsedTime.getElapsedTime().asSeconds() >= 10.f) ended = true;

}

{ // mFinished may be accessed from multiple threads, protect sf::Lock lock(mMutex);

mFinished = true;

}

}

You may have noticed that there is a lock in every read or write of the shared variables. The extra brackets are just a way of releasing that same lock as soon as possible, so the variable is then available for other threads to access.

All this function does is it remains in a while loop until the clock has ticked for ten seconds and then lets the thread finish after setting mFinished to true.

[ 135 ]

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]