I thought I'd write a little tutorial about using threads with BlitzMAX. Threads are currently still experimental. The tutorial presented here is not complete and may even contain inaccuracies.
An overview of the experimental thread module.
This is not exactly a tutorial, but rather a quick overview of using threads in BlitzMAX. In order to use threads, you must use the svn version of BlitzMAX. I am not going to go into details on how to install svn, you can find info here.
http://www.blitzbasic.com/Community/posts.php?topic=74439 <- for svn info
http://www.blitzbasic.com/Community/posts.php?topic=80344 <- for getting threading capabilities up and running.
Now on to the tutorial
Part 1. Creating threads.
First you need a thread to run. This is done by creating a function that takes an object as its parameter and returns an object.
That's it! You now have a thread you can spawn any time. To spawn the thread, just make a call to CreateThread(). CreateThread() takes two parameters, a reference to the thread function and the object to pass to it. CreateThread() then returns a handle which is then used to close the thread later on.
That's all. You now have a runnable program. If you try running the above 5 lines, you might find that sometimes it prints "Hi! I'm a thread" and sometimes not. That's because when the main program exits, all running threads are automatically closed. Since the program ends immediately after spawning the thread, it's possible that it will be closed before the Print can take place. So we'll add a delay in the example.
Part 2. Closing thread handles
Closing a thread handle means freeing it's handle back to the system so it can be used for other purposes. This is different from terminating a thread. Termination happens automatically when the thread ends or when the program exits.
There are two ways to close a thread handle. WaitThread() will wait until a thread finishes, then will close its handle. DetachThread() will close the handle immediately, allowing the calling thread to continue. The detached thread will continue to execute. This means you do not have to worry about polling the thread continuously to see if it has ended or not.
In our example above, we will replace the delay() with WaitThread(). Since WaitThread() also returns an object, we will make use of that as well.
Part 3. Mutex
Mutexes are basically gatekeepers to shared resources. If two threads need access to the same resource at the same time, then unpredictable results could happen. Anything from incorrect data to crashes could happen. Mutexes are not necessary all the time, if threads are only going to read data but not write to it, for example.
First I'm going to show you an example of threads sharing a resource without mutexes. The resource will be the console and the threads will try and print messages concurrently.
Wow, what a mess. Letters from one thread are printing between letters from the other. To prevent that, let's use a mutex.
To create the mutex, just use CreateMutex(). CreateMutex() returns a handle which can be locked and unlocked when needed. When a thread needs access to a resource, just call LockMutex(). If the mutex has already been locked by another thread, then the thread will wait until it has been unlocked before locking it itself. When the thread is through with the resource, it calls UnlockMutex() so that other threads can use it as well.
Now everything prints as expected. Or does it? You might actually see the Good Bye lines printed before the Hello lines in certain circumstances. It just depends on which thread gets to that LockMutex() first. So how would you make sure threads work in a certain order? For that, you can use Conditions.
Part Whatever: Conditions
Conditions are like traffic signals. You sit at a red light until it turns green. then you go.
To create a condition, you use CreateCond(). CreateCond() will return a handle. You will also need to create a mutex to use with the condition.
To wait for the condition to be signaled, you use WaitCond(). The parameters for WaitCond() are the condition handle and the mutex handle. To signal a condition, you use either SignalCond() or BroadcastCond(). Both take the condition handle as the only parameter. WaitCond(), SignalCond(), and BroadcastCond() all need to be wrapped with LockMutex() and UnlockMutex() commands, using the mutex created for the conditions.
The difference between SignalCond() and BroadcastCond() is that SignalCond() will only signal one thread to continue. If more than one thread is waiting for the signal, the one that gets the signal will be random. BroadcastCond() will signal all threads waiting for the signal.
An overview of the experimental thread module.
This is not exactly a tutorial, but rather a quick overview of using threads in BlitzMAX. In order to use threads, you must use the svn version of BlitzMAX. I am not going to go into details on how to install svn, you can find info here.
http://www.blitzbasic.com/Community/posts.php?topic=74439 <- for svn info
http://www.blitzbasic.com/Community/posts.php?topic=80344 <- for getting threading capabilities up and running.
Now on to the tutorial
Part 1. Creating threads.
First you need a thread to run. This is done by creating a function that takes an object as its parameter and returns an object.
SuperStrict Function Hello:Object(data:Object) Print "Hi! I'm a thread!" End Function
That's it! You now have a thread you can spawn any time. To spawn the thread, just make a call to CreateThread(). CreateThread() takes two parameters, a reference to the thread function and the object to pass to it. CreateThread() then returns a handle which is then used to close the thread later on.
Local Thread:Int = CreateThread(Hello,Null)
That's all. You now have a runnable program. If you try running the above 5 lines, you might find that sometimes it prints "Hi! I'm a thread" and sometimes not. That's because when the main program exits, all running threads are automatically closed. Since the program ends immediately after spawning the thread, it's possible that it will be closed before the Print can take place. So we'll add a delay in the example.
SuperStrict Function Hello:Object(data:Object) 'The thread function Print "Hi! I'm a thread!" End Function Local Thread:Int = CreateThread(Hello,Null) 'Create the thread Delay(1000) 'Delay to allow the thread to complete
Part 2. Closing thread handles
Closing a thread handle means freeing it's handle back to the system so it can be used for other purposes. This is different from terminating a thread. Termination happens automatically when the thread ends or when the program exits.
There are two ways to close a thread handle. WaitThread() will wait until a thread finishes, then will close its handle. DetachThread() will close the handle immediately, allowing the calling thread to continue. The detached thread will continue to execute. This means you do not have to worry about polling the thread continuously to see if it has ended or not.
In our example above, we will replace the delay() with WaitThread(). Since WaitThread() also returns an object, we will make use of that as well.
SuperStrict Function Hello:Object(data:Object) 'The thread function Print "Hi! I'm a thread!" Return "I am a returned object!" 'This will be returned to the WaitThread() function which then returns it to the main program End Function Local Thread:Int = CreateThread(Hello,Null) 'create the thread 'WaitThread will halt the main thread until the called thread ends 'It then returns the object that was returned by the thread Print String(WaitThread(Thread))
Part 3. Mutex
Mutexes are basically gatekeepers to shared resources. If two threads need access to the same resource at the same time, then unpredictable results could happen. Anything from incorrect data to crashes could happen. Mutexes are not necessary all the time, if threads are only going to read data but not write to it, for example.
First I'm going to show you an example of threads sharing a resource without mutexes. The resource will be the console and the threads will try and print messages concurrently.
SuperStrict Function Hello:Object(data:Object) 'The hello thread Print "Hello, everyone" Print "Hello, I am going to print a few lines of data" Print "Hello, All the data will start with Hello" Print "Hello, So you know which thread it came from" Print "Hello, run this program a few times" Print "Hello, so you can see the unpredictable results." End Function Function GoodBye:Object(data:Object) 'the goodbye thread Print "Good bye, everyone" Print "Good bye, another list of data to be printed" Print "Good bye, This will have good bye preceding it" Print "Good bye, ideally, you would have all the goodbye" Print "Good bye, lines printed together and all the" Print "Good bye, Hello lines printed together" End Function DetachThread(CreateThread(Hello,Null)) 'creates the thread, then detaches it from the main program DetachThread(CreateThread(GoodBye,Null)) Delay(1000) 'a delay to allow the threads to finish their task
Wow, what a mess. Letters from one thread are printing between letters from the other. To prevent that, let's use a mutex.
To create the mutex, just use CreateMutex(). CreateMutex() returns a handle which can be locked and unlocked when needed. When a thread needs access to a resource, just call LockMutex(). If the mutex has already been locked by another thread, then the thread will wait until it has been unlocked before locking it itself. When the thread is through with the resource, it calls UnlockMutex() so that other threads can use it as well.
SuperStrict Global Mutex:Int = CreateMutex() 'create the mutex. This mutex will be locked anytime a thread needs access to the consol Function Hello:Object(data:Object) LockMutex(Mutex) 'Lock the mutex so we can have exclusive access to the consol Print "Hello, everyone" Print "Hello, I am going to print a few lines of data" Print "Hello, All the data will start with Hello" Print "Hello, So you know which thread it came from" Print "Hello, run this program a few times" Print "Hello, so you can see the unpredictable results." UnlockMutex(mutex) 'unlock the mutex to allow other threads access End Function Function GoodBye:Object(data:Object) LockMutex(Mutex) 'lock the mutex blah blah Print "Good bye, everyone" Print "Good bye, another list of data to be printed" Print "Good bye, This will have good bye preceding it" Print "Good bye, ideally, you would have all the goodbye" Print "Good bye, lines printed together and all the" Print "Good bye, Hello lines printed together" UnlockMutex(Mutex) 'blah again End Function DetachThread(CreateThread(Hello,Null)) DetachThread(CreateThread(GoodBye,Null)) Delay(1000) 'delay to allow threads to finish
Now everything prints as expected. Or does it? You might actually see the Good Bye lines printed before the Hello lines in certain circumstances. It just depends on which thread gets to that LockMutex() first. So how would you make sure threads work in a certain order? For that, you can use Conditions.
Part Whatever: Conditions
Conditions are like traffic signals. You sit at a red light until it turns green. then you go.
To create a condition, you use CreateCond(). CreateCond() will return a handle. You will also need to create a mutex to use with the condition.
To wait for the condition to be signaled, you use WaitCond(). The parameters for WaitCond() are the condition handle and the mutex handle. To signal a condition, you use either SignalCond() or BroadcastCond(). Both take the condition handle as the only parameter. WaitCond(), SignalCond(), and BroadcastCond() all need to be wrapped with LockMutex() and UnlockMutex() commands, using the mutex created for the conditions.
SuperStrict Global Mutex:Int = CreateMutex() 'create a mutex to use with the condition Global Cond:Int = CreateCond() 'create a condition Function Hello:Object(data:Object) 'since no other threads will be using the console til this is done, there is no need to use LockMutex() Print "Hello, everyone" Print "Hello, I am going to print a few lines of data" Print "Hello, All the data will start with Hello" Print "Hello, So you know which thread it came from" Print "Hello, run this program a few times" Print "Hello, so you can see the unpredictable results." LockMutex(Mutex) 'SignalCond() needs to be wrapped with LockMutex() and UnlockMutex() SignalCond(Cond) 'Signal the next thread to continue UnlockMutex(mutex) End Function Function GoodBye:Object(data:Object) LockMutex(Mutex) 'WaitCond() needs to be wrapped with LockMutex() and unlockMutex() WaitCond(Cond,Mutex) 'Wait for the condition to be signaled before continuing UnlockMutex(Mutex) Print "Good bye, everyone" Print "Good bye, another list of data to be printed" Print "Good bye, This will have good bye preceding it" Print "Good bye, ideally, you would have all the goodbye" Print "Good bye, lines printed together and all the" Print "Good bye, Hello lines printed together" End Function DetachThread(CreateThread(Hello,Null)) DetachThread(CreateThread(GoodBye,Null)) Delay(1000)
The difference between SignalCond() and BroadcastCond() is that SignalCond() will only signal one thread to continue. If more than one thread is waiting for the signal, the one that gets the signal will be random. BroadcastCond() will signal all threads waiting for the signal.