Lecture #10: Threads & Synchronization
Lecture #10: Threads & Synchronization
Processes vs. Threads Creating threads Locking mechanisms Race conditions & deadlocks Volatile
Threads in games
Threads are very common in game engines
Render thread Game/Event loop Different threads for AI, physics, Other update threads
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
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() { } };
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_; }
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; }
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; }
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
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
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!
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