An overview of the experimental thread module.

BlitzMax Forums/BlitzMax Tutorials/An overview of the experimental thread module.

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.
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.

Cool, this is nice and easy to follow, thanks for writing it and explaining it clearly!

You should use code tags for the smaller/one liners instead of codeboxes, as they make reading a bit hazy.

Looks good! (reading now)

Very interesting read, thanks a lot!

Explains this stuff very well. Thanks!