BlitzMax/Modules/System/Threads

Welcome to the weird and wonderful world of multithreading!

Multithreading effectively allows your programs to do several things at the same time. The word 'thread' in this context means 'thread of execution' - or, the series of instructions, branches and so on executed by your program. Most programs are 'single threaded', meaning there is only one thread of execution. However, more and more programs are using multiple threads.

Multithreading used to be achieved by software trickery, which made threading useful but not really faster - there was still only one CPU pretending to do multiple things at the same time! But these days, multicore CPUs mean that threading can be used to truly do multiple things at the same time (or 'in parallel').

Creating a thread is easy - just call CreateThread. You will need to provide a function for the thread to use as its 'entry point'. Once the thread is created, this function will start executing in parallel with the code that called CreateThread. When the thread function returns, that thread will be 'terminated'.

Alas, threading turns out to be rather tricky due to an issue known as 'synchronization'. Synchronization is required when you need to prevent multiple threads from modifying or accessing the same data at the same time. Synchronization usually involves a thread 'blocking'. When a thread blocks, it completely halts execution until another thread does something that causes it to 'unblock' and resume execution.

BlitzMax provides 2 primitives known as 'mutexes' and 'semaphores' to assist with synchronization:

  • Mutexes provide a simple locking mechanism. Only one thread at a time can lock a mutex (using LockMutex or TryLockMutex), so this is an easy way to protect resources from simultaneous access. If a thread calls LockMutex and the mutex is already locked by another thread, the current thread will block until the other thread releases the mutex using UnlockMutex. So don't forget to UnlockMutex a mutex after you are finished with it!
  • Semaphores provide a synchronized counting mechanism, and contain an internal integer counter. There are 2 operations you can perform on a semaphore - post and wait. Posting a semaphore (using PostSemaphore) causes the semaphore's internal counter to be incremented, while waiting for a semaphore (using WaitSemaphore) will cause the current thread to block until the semaphore's internal counter is greater than 0. When it is, the counter is decremented and the thread unblocks. Semaphores are very useful for producer/consumer type situations.

TypesEdit

TThreadEdit

Thread Type

Methods
  • Detach
  • Wait
  • Running
Functions
  • Create
  • Main
  • Current

TThread: MethodsEdit

Detach

Method Detach()

Description: Detach this thread

Wait

Method Wait:Object()

Description: Wait For this thread To finish

Returns: The Object returned by the thread.

Running

Method Running()

Description: Check if this thread is running

TThread: FunctionsEdit

Create

Function Create:TThread( entry:Object( data:Object),data:Object )

Description: Create a new thread

Main

Function Main:TThread()

Description: Get Main thread

Returns: A thread object representing the main application thread.

Current

Function Current:TThread()

Description: Get current thread

Returns: A thread object representing the current thread.

TThreadDataEdit

ThreadData type

Methods
  • SetValue
  • GetValue
Functions
  • Create

TThreadData: MethodsEdit

SetValue

Method SetValue( value:Object )

Description: Set thread data value

GetValue

Method GetValue:Object()

Description: Get thread data value

TThreadData: FunctionsEdit

Create

Function Create:TThreadData()

Description: Create thread data

TMutexEdit

Mutex type

Methods
  • Close
  • Lock
  • TryLock
  • Unlock
Functions
  • Create

TMutex: MethodsEdit

Close

Method Close()

Description: Close the mutex

Lock

Method Lock()

Description: Lock the mutex

TryLock

Method TryLock()

Description: Try to lock the mutex

Returns: True if mutex was successfully locked; False if mutex was already locked by another thread.

Unlock

Method Unlock()

Description: Unlock the mutex

TMutex: FunctionsEdit

Create

Function Create:TMutex()

Description: Create a new mutex

TSemaphoreEdit

Semaphore Type

Methods
  • Close
  • Wait
  • Post
Functions
  • Create

TSemaphore: MethodsEdit

Close

Method Close()

Description: Close the semaphore

Wait

Method Wait()

Description: Wait for the semaphore

Post

Method Post()

Description: Post the semaphore

TSemaphore: FunctionsEdit

Create

Function Create:TSemaphore( count )

Description: Create a new semaphore

TCondVarEdit

CondVar type

Methods
  • Close
  • Wait
  • Signal
  • Broadcast
Functions
  • Create

TCondVar: MethodsEdit

Close

Method Close()

Description: Close the condvar

Wait

Method Wait( mutex:TMutex )

Description: Wait for the condvar

Signal

Method Signal()

Description: Signal the condvar

Broadcast

Method Broadcast()

Description: Broadcast the condvar

TCondVar: FunctionsEdit

Create

Function Create:TCondVar()

Description: Create a new condvar

FunctionsEdit

CreateThreadEdit

Function CreateThread:TThread( entry:Object( data:Object ),data:Object )

Description: Create a thread

Returns: A new thread object.

Information: Creates a thread and returns a thread object.

The value returned by the thread entry routine can be later retrieved using WaitThread.

To 'close' a thread, call either DetachThread or WaitThread. This isn't strictly necessary as the thread will eventually be closed when it is garbage collected, however, it may be a good idea if you are creating many threads very often, as some operating systems have a limit on the number of threads that can be allocated at once.

Example:


'Make sure to have 'Threaded build' enabled!
'
Strict

'Custom print that shows which thread is doing the printing
Function MyPrint( t$ )
	If CurrentThread()=MainThread() 
		Print "Main thread: "+t
	Else
		Print "Child thread: "+t
	EndIf
End Function

'Our thread function
Function MyThread:Object( data:Object )

	'show data we were passed
	Myprint data.ToString()

	'do some work
	For Local i=1 To 1000
		MyPrint "i="+i
	Next
	
	'return a value from the thread
	Return "Data returned from child thread."
	
End Function

MyPrint "About to start child thread."

'create a thread!
Local thread:TThread=CreateThread( MyThread,"Data passed to child thread." )

'wait for thread to finish and print value returned from thread
MyPrint WaitThread( Thread ).ToString()

MainThreadEdit

Function MainThread:TThread()

Description: Get Main thread

Returns: A thread Object representing the Main application thread.

CurrentThreadEdit

Function CurrentThread:TThread()

Description: Get current thread

Returns: A thread object representing the current thread.

DetachThreadEdit

Function DetachThread( thread:TThread )

Description: Detach a thread

Information: DetachThread closes a thread's handle, but does not halt or otherwise affect the target thread.

Once one a thread has been detached, it wil no longer be possible to use WaitThread to get its return value.

This allows the thread to run without your program having to continually check whether it has completed in order to close it.

WaitThreadEdit

Function WaitThread:Object( thread:TThread )

Description: Wait for a thread to finish

Returns: The object returned by the thread entry routine.

Information: WaitThread causes the calling thread to block until the target thread has completed execution.

If the target thread has already completed execution, WaitThread returns immediately.

The returned object is the object returned by the thread's entry routine, as passed to CreateThread.

ThreadRunningEdit

Function ThreadRunning( thread:TThread )

Description: Check if a thread is running

Returns: True if thread is still running, otherwise False.

CreateThreadDataEdit

Function CreateThreadData:TThreadData()

Description: Create thread data

Returns: A new thread data object.

SetThreadDataValueEdit

Function SetThreadDataValue( data:TThreadData,value:Object )

Description: Set thread data value

GetThreadDataValueEdit

Function GetThreadDataValue:Object( data:TThreadData )

Description: Get thread data value

CreateMutexEdit

Function CreateMutex:TMutex()

Description: Create a mutex

Returns: A new mutex object

Example:


'Make sure to have 'Threaded build' enabled!
'
Strict

'a global list that multiple threads want to modify
Global list:TList=New TList

'a mutex controlling access to the global list
Global listMutex:TMutex=CreateMutex()

Function MyThread:Object( data:Object )

	For Local item=1 To 10
		'simulate 'other' processing...
		Delay Rand( 10,50 )

		'lock mutex so we can safely modify global list
		LockMutex listMutex

		'modify list
		list.AddLast "Thread "+data.ToString()+" added item "+item

		'unlock mutex
		UnlockMutex listMutex
	Next
	
End Function

Local threads:TThread[10]

'Create worker threads
For Local i=0 Until 10
	threads[i]=CreateThread( MyThread,String( i+1 ) )
Next

Print "Waiting for worker threads..."

'Wait for worker threads to finish
For Local i=0 Until 10
	WaitThread threads[i]
Next

'Show the resulting list
'
'Note: We don't really have to lock the mutex here, as there are no other threads running.
'Still, it's a good habit to get into.
LockMutex listMutex
For Local t$=EachIn list
	Print t
Next
UnlockMutex listMutex

CloseMutexEdit

Function CloseMutex( mutex:TMutex )

Description: Close a mutex

LockMutexEdit

Function LockMutex( mutex:TMutex )

Description: Lock a mutex

TryLockMutexEdit

Function TryLockMutex( mutex:TMutex )

Description: Try to lock a mutex

Returns: True if mutex was successfully locked; False if mutex was already locked by another thread.

UnlockMutexEdit

Function UnlockMutex( mutex:TMutex )

Description: Unlock a mutex

CreateSemaphoreEdit

Function CreateSemaphore:TSemaphore( count )

Description: Create a semaphore

Returns: A new semaphore object

Example:


'Make sure to have 'Threaded build' enabled!
'
Strict

'a simple queue
Global queue$[100],put,get

'a counter semaphore
Global counter:TSemaphore=CreateSemaphore( 0 )

Function MyThread:Object( data:Object )

	'process 100 items
	For Local item=1 To 100
	
		'add an item to the queue
		queue[put]="Item "+item
		put:+1
		
		'increment semaphore count.
		PostSemaphore counter
	
	Next
		
End Function

'create worker thread
Local thread:TThread=CreateThread( MyThread,Null )

'receive 100 items
For Local i=1 To 100

	'Wait for semaphore count to be non-0, then decrement.
	WaitSemaphore counter
	
	'Get an item from the queue
	Local item$=queue[get]
	get:+1
	
	Print item

Next

CloseSemaphoreEdit

Function CloseSemaphore( semaphore:TSemaphore )

Description: Close a semaphore

WaitSemaphoreEdit

Function WaitSemaphore( semaphore:TSemaphore )

Description: Wait for a semaphore

PostSemaphoreEdit

Function PostSemaphore( semaphore:TSemaphore )

Description: Post a semaphore

CreateCondVarEdit

Function CreateCondVar:TCondVar()

Description: Create a condvar

Returns: A new condvar object

CloseCondVarEdit

Function CloseCondVar( condvar:TCondVar )

Description: Close a condvar

WaitCondVarEdit

Function WaitCondVar( condvar:TCondVar,mutex:TMutex )

Description: Wait for a condvar

SignalCondVarEdit

Function SignalCondVar( condvar:TCondVar )

Description: Signal a condvar

BroadcastCondVarEdit

Function BroadcastCondVar( condvar:TCondVar )

Description: Broadcast a condvar

CompareAndSwapEdit

Function CompareAndSwap( target Var,oldValue,newValue )

Description: Compare and swap

Returns: True if target was updated

Information: Atomically replace target with new_value if target equals old_value.

AtomicAddEdit

Function AtomicAdd( target Var,value )

Description: Atomic add

Returns: Previous value of target

Information: Atomically add value to target.

AtomicSwapEdit

Function AtomicSwap( target Var,value )

Description: Atomically swap values

Returns: The old value of target