You are on page 1of 7

Overview

Processes vs. Threads Creating threads Locking mechanisms Race conditions & deadlocks Volatile

Game Programming in C++


Arjan Egges Lecture #10: Threads & synchronization

Threads in games
Threads are very common in game engines
Render thread Game/Event loop Different threads for AI, physics, Other update threads

Threads in game engines


Why is this a problem? Not all libraries and engines are threadsafe Game/graphics engines may have ego problems
The game is built around me, so I control how threads are used! GUI libraries vs. Graphics engines

Challenging task to ensure stability

Processes vs. Threads


What is a process?
An OS entity that provides the context for:
Executing program instructions Managing resources (memory, I/O handles, and so on)

Processes vs. Threads


Each process must have at least one path of execution
Main thread

A thread is a path of execution


Threads share the same OS address space
Cheap data exchange

A process is protected from other OS processes via memory management Every process has its own address space

Threads can be stopped, started, paused, and new threads can be created Threads are not protected (blocking or aborting a thread could influence the whole program

Threads
Multithreading does not automatically increase performance (in fact, quite the opposite!):
Multiple threads accessing the same data can result in a lot of synchronization overhead

Thread scheduling
Win32 threads are scheduled according to the round-robin principle
This includes multiprocessor machines Windows stays in control of how long each thread gets executed

Starting a thread
Eager spawning On-demand spawning

In a cycle, each thread gets allocated a time slice Threads can have different priorities

Creating a Win32 thread


#include <windows.h> // Win32 threads #include <iostream> #include <string> using namespace std; DWORD WINAPI MyThread(LPVOID n) { std::string name = string(n); for (int i=0;i<100; i++) { cout << "Thread " << name << ", i = "; cout << i << endl; } return 0; }

Creating a Win32 thread


void main() { DWORD iID[2]; HANDLE hThreads[2]; DWORD waiter; // flag // create two threads hThreads[0] = CreateThread(NULL, 0,MyThread,"T1", NULL,&iID[0]); hThreads[1] = CreateThread(NULL, 0,MyThread,"T2", NULL,&iID[1]);

Creating a Win32 thread


// wait until threads have finished waiter = WaitForMultipleObjects(2,hThreads, true,INFINITE); return; }

Java threads
But not as clean as Java threads
class SimpleThread extends Thread { public SimpleThread(String str) { super(str); } public void run() { for (int i = 0; i < 10; i++) { System.out.println(i+" "+getName()); sleep(1000); } System.out.println("DONE! "+getName()); } }

OpenThreads
So, how to make dealing with threads in C/C++ OO-compliant? This implementation is Windows-specific
For Linux or other OS reimplementation is required

OpenThreads
class MyThread : public OpenThreads::Thread { public: MyThread() : Thread() { } virtual ~MyThread() { } // Overriding thread running method from // OpenThreads. void run() { } };

Solution: use platform-independent OO thread library, such as OpenThreads, or Boost::Thread

Traps and pitfalls


Race condition (Singleton example):
class Singleton { public: static Singleton* instance(); void other_method(); // other methods private: static Singleton* pInstance_; };

Traps and pitfalls


Singleton* Singleton::pInstance_ = NULL; Singleton *Singleton::instance() { if ( !pInstance_ ) pInstance_ = new Singleton(); return pInstance_; }

This is not thread-safe!


Suppose thread A and thread B both like to use the Singleton before it is initialized

Traps and pitfalls


Thread A evaluates the if-condition and considers its value as a NULL pointer Thread A is suspended Thread B evaluates the if-condition, reads the NULL pointer and create the Singleton instance Thread B is suspended Thread A gets the control again. It continues to create the Singleton Now a second instance has been created!

Locking mechanisms
Mutexes and Guards Critical Sections Semaphores Other types of locking mechanisms:
Condition Variables Reader/Writer blocks

Locking mechanisms
Using a mutex to control access:
class Singleton { public: static Singleton* instance(); void other_method(); // other methods private: static OpenThreads::Mutex mutex_; static Singleton* pInstance_; };

Locking mechanisms
Adapting the instance() method:
Singleton *Singleton::instance() { mutex_.lock(); if ( !pInstance_ ) pInstance_ = new Singleton(); mutex_.unlock(); return pInstance_; }

Disadvantages:
Unlock required for each method return Efficiency

Locking mechanisms
Create a Guard class:
class Guard { public: Guard(OpenThread::Mutex& m) : mutex_(m) { mutex_.lock(); } virtual ~Guard() { mutex_.unlock(); } private: OpenThreads::Mutex& mutex_; };

Locking mechanisms
The new Singleton::instance() method:
Singleton *Singleton::instance() { Guard g(mutex_); if ( !pInstance_ ) pInstance_ = new Singleton(); return pInstance_; }

Boost has a Guard class

Locking mechanisms
Trying to improve efficiency
Singleton *Singleton::instance() { if ( !pInstance_ ) { Guard(mutex_); pInstance_ = new Singleton(); } return pInstance_; }

Locking mechanisms
Double Checked Locking Pattern:
Singleton *Singleton::instance() { if ( !pInstance_ ) { Guard(mutex_); if ( !pInstance_ ) pInstance_ = new Singleton(); } return pinstance; }

Will this work? Why/why not?

Will this work?

Locking mechanisms
Consider this part of the DLCP implementation: This does three things:
1. Allocate memory (via operator new) to hold the Singleton object. 2. Construct the Singleton object in the memory. 3. Assign to pInstance_ the address of the memory. pInstance_ = // (3) operator new( sizeof(Singleton) ); // (1) new (pInstance_) Singleton(); // (2) pInstance_ = new Singleton();

Locking mechanisms
In context again:
Singleton *Singleton::instance() { if ( !pInstance_ ) { Guard(mutex_); if ( !pInstance_ ) { pInstance_ = operator new( sizeof(Singleton) ); new (pInstance_) Singleton(); } } return pinstance; }

Order of generated code: 1, 3, 2

Locking mechanisms
Consider this scenario: Thread A executes (1) and (3) and is suspended. Now pInstance_ is not NULL, however the instance has not been constructed. Thread B checks the outmost condition, sees pInstance_ not NULL Thread B continues to return the pointer to the non-initialized Singleton object.

Locking mechanisms
Unfortunately there is no real solution for these types of DCLP issues A (dirty) workaround here:
Leave mutex at the start of the instance() method Keep a local copy of the Singleton pointer

Critical Section
You can lock/unlock mutexes at different places in the code to establish critical sections
void MyClass::someMethod() { // do some stuff here mutex_.lock(); // enter critical section // do critical stuff here mutex_.unlock(); // leave critical section // do other stuff here }

Semaphores
A semaphore is an object that limits the number of threads gaining simultaneous access to itself
keeps an internal count of accessing threads may optionally store references to the threads

Can be used for:


Limiting number of concurrent database connections The number of players connected to a server

Deadlock
Example:
Guard(mutex1_); Guard(mutex2_); // do relevant stuff here Guard(mutex2_); Guard(mutex1_); // do other relevant stuff here Thread A

Deadlock
Classic: dining philosophers problem Four conditions for deadlocks to occur:
Mutual exclusion. At least one resource used by the threads must not be shareable. At least one process must be holding a resource and waiting to acquire a resource currently held by another process. A resource cannot be preemptively taken away from a process. Processes only release resources as a normal event. Circular waiting of processes on each other

Thread B

This can lead to deadlock

Deadlock
Solution in the case of philosophers:
Introduce non-similarity in picking up chopsticks
class BattleGame { public:

Volatile
Consider the following simple class:

There is no language support to help prevent deadlock But generally, deadlocks can be avoided by careful design Its up to you!

void showScoreOverlay(); void fight(); // other methods private: bool finishedFighting_; };

Volatile
Two threads running concurrently:
void BattleGame::showScoreOverlay() { Thread A while (!finishedFighting_) sleep(100); // thread sleeps for 100ms GraphicsEngine::createOverlay(theScore_); } void BattleGame::fight() { finishedFighting_ = false; AISystem::fight(); finishedFighting_ = true; } Thread B

Volatile
Due to optimizations, this will not work The showScoreOverlay() method will be optimized by the compiler
Thread A will deadlock

Optimizations can be turned off using the volatile keyword In the BattleGame class:
volatile bool finishedFighting_;

Summary
Threads & processes Mutexes and critical sections Deadlocks Next lecture: Exceptions

You might also like