Semaphores
Last Updated 3/28/10
When you have multiple threads running you have to make sure that they don't try to manipulate the same piece of data at the same time. In this tutorial we'll use semaphores to lock threads to prevent them from crashing into each other.Semaphores tutorial with SDL 2 is now available.
Before we start there's something that has to be said.
In this tutorial we have video functions running in separate threads.
You should never do this in a real application.
It's just bad software design and in some cases can cause your OS to become unstable.
The only reason we're doing it here is because it's a small program and nothing's going to go wrong.
We're doing it here just as a simple demonstration of semaphores in action.
Now with that out of the way let's start with the tutorial. In this program we're going to have two threads trying to blit surfaces to screen and update the screen at the same time. Having two threads trying to manipulate the same data at the same time is a recipe for disaster. This is where the semaphores come into play. What the semaphore will do is allow only one thread to manipulate to the screen at a time.
Now with that out of the way let's start with the tutorial. In this program we're going to have two threads trying to blit surfaces to screen and update the screen at the same time. Having two threads trying to manipulate the same data at the same time is a recipe for disaster. This is where the semaphores come into play. What the semaphore will do is allow only one thread to manipulate to the screen at a time.
//The threads that will be used
SDL_Thread *threadA = NULL;
SDL_Thread *threadB = NULL;
//The protective semaphore
SDL_sem *videoLock = NULL;
At the top of our program we have our two threads we're going to have running along with our semaphore.
bool init()
{
//Initialize all SDL subsystems
if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )
{
return false;
}
//Set up the screen
screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE );
//If there was an error in setting up the screen
if( screen == NULL )
{
return false;
}
//Initialize SDL_ttf
if( TTF_Init() == -1 )
{
return false;
}
//Create the semaphore
videoLock = SDL_CreateSemaphore( 1 );
//Set the window caption
SDL_WM_SetCaption( "Testing Threads", NULL );
//If everything initialized fine
return true;
}
Before we can use a semaphore you have to create it using SDL_CreateSemaphore().
You may be wondering what that 1 argument does. I'll explain later.
You may be wondering what that 1 argument does. I'll explain later.
int blitter_a( void *data )
{
//Y offset
int y = 10;
//Go through the surface
for( int b = 0; b < 5; b++ )
{
//Wait
SDL_Delay( 200 );
//Show surface
show_surface( ( ( SCREEN_WIDTH / 2 ) - text[ b ]->w ) / 2, y, text[ b ] );
//Move down
y += 100;
}
return 0;
}
int blitter_b( void *data )
{
//Y offset
int y = 10;
//Go through the surface
for( int b = 0; b < 5; b++ )
{
//Wait
SDL_Delay( 200 );
//Show surface
show_surface( ( SCREEN_WIDTH / 2 ) + ( ( ( SCREEN_WIDTH / 2 ) - text[ b ]->w ) / 2 ), y, text[ b ] );
//Move down
y += 100;
}
return 0;
}
Here are our threads functions.
All they do is go through an array of surfaces and show them on the screen.
blitter_a() blits surfaces on the left of the screen and blitter_b() blits surfaces on the right.
As you can see we're not using our usual apply_surface() function. We're using show_surface(), a modified version of apply_surface() which applies the surface using SDL_BlitSurface() and updates the screen using SDL_Flip() all in one function. You'll see it in a little bit.
Since we're going to have the threads run at the same time we have prevent them from trying to manipulate the screen at the same time. To do this we're going to have to protect the show_surface() function using our semaphore.
As you can see we're not using our usual apply_surface() function. We're using show_surface(), a modified version of apply_surface() which applies the surface using SDL_BlitSurface() and updates the screen using SDL_Flip() all in one function. You'll see it in a little bit.
Since we're going to have the threads run at the same time we have prevent them from trying to manipulate the screen at the same time. To do this we're going to have to protect the show_surface() function using our semaphore.
void show_surface( int x, int y, SDL_Surface* source )
{
//Lock
SDL_SemWait( videoLock );
//Holds offsets
SDL_Rect offset;
//Get offsets
offset.x = x;
offset.y = y;
//Blit
SDL_BlitSurface( source, NULL, screen, &offset );
//Update the screen
SDL_Flip( screen );
//Unlock
SDL_SemPost( videoLock );
}
Here's our show_surface() function which blits a surface then updates the screen.
We also put SDL_SemWait() at the top of our function and SDL_SemPost() at the bottom.
What SDL_SemWait() does is lock the semaphore:
So when another thread tries to get through, it has to wait:
Until SDL_SemPost() is called to unlock the semaphore:
Then the next thread does its thing and locks the semaphore behind it:
Because SDL_BlitSurface() and SDL_Flip() are protected by the semaphore, only one thread can call video functions at a time so there's no conflict.
As I mentioned earlier we passed 1 argument in SDL_CreateSemaphore() in our init() function. That 1 is how many threads can get past the semaphore before it locks. If we wanted to we could have allowed 2 threads to pass, but that would be pointless in this case. Most of the time you will only want one thread to pass through a semaphore before locking.
What SDL_SemWait() does is lock the semaphore:
So when another thread tries to get through, it has to wait:
Until SDL_SemPost() is called to unlock the semaphore:
Then the next thread does its thing and locks the semaphore behind it:
Because SDL_BlitSurface() and SDL_Flip() are protected by the semaphore, only one thread can call video functions at a time so there's no conflict.
As I mentioned earlier we passed 1 argument in SDL_CreateSemaphore() in our init() function. That 1 is how many threads can get past the semaphore before it locks. If we wanted to we could have allowed 2 threads to pass, but that would be pointless in this case. Most of the time you will only want one thread to pass through a semaphore before locking.
//Show the background
show_surface( 0, 0, background );
//Create and run the threads
threadA = SDL_CreateThread( blitter_a, NULL );
threadB = SDL_CreateThread( blitter_b, NULL );
//Wait for the threads to finish
SDL_WaitThread( threadA, NULL );
SDL_WaitThread( threadB, NULL );
//While the user hasn't quit
while( quit == false )
{
//If there's an event to handle
if( SDL_PollEvent( &event ) )
{
//If the user has Xed out the window
if( event.type == SDL_QUIT )
{
//Quit the program
quit = true;
}
}
}
In the main() thread after everything is loaded and initialized we show the background on the screen.
Then we run our two threads and wait for them to finish by using SDL_WaitThread(). After the threads are finished we simply wait for the user to quit.
Then we run our two threads and wait for them to finish by using SDL_WaitThread(). After the threads are finished we simply wait for the user to quit.
void clean_up()
{
//Destroy semaphore
SDL_DestroySemaphore( videoLock );
//Free the surfaces
SDL_FreeSurface( background );
//Free text
for( int t = 0; t < 5; t++ )
{
SDL_FreeSurface( text[ t ] );
}
//Close the font
TTF_CloseFont( font );
//Quit SDL_ttf
TTF_Quit();
//Quit SDL
SDL_Quit();
}
Don't forget to clean up your semaphore when you're done with it.
We do that in our clean up function using SDL_DestroySemaphore().
We do that in our clean up function using SDL_DestroySemaphore().