Some Multi-Threading Tips...

BlitzMax Forums/BlitzMax Programming/Some Multi-Threading Tips...

jondecker76(Posted 2010) [#1]
Hello. I'm very new to BMax, but I have just successfully completed my first application (a photobooth application). Since my webcam framerate was 15 FPS, it was dragging my entire application down to 15 FPS, as execution halted while the program was grabbing the webcam image frames. I wanted to add a thread who's sole purpose was to do a loop, grabbing webcam frames (at the horribly slow 15fps), so that my main application was free to run at higher framerates (I use some animations and such that looked horrible at 15 FPS)
I also wanted to make it so that multithreading could be turned on and off (I.e. I didn't want to hard-code it to be multithreading). Below is the solution I came up with (pseudocode) that allows my application to run both threaded and non-threaded. I wanted to share this, as I'm sure it could help other people out!
(My application went from 15 FPS to over 50 by threading my camera capture code!)

NOTE: The following code will not compile - it is for illustration purposes only and is made as simple as possible to illustrate the technique!


TCam.bmx:
Type TCam
	Field img:TImage
	Field mux:TMutex


	Method Update()
		'The update() method can be run from the main application, or a separate thread.
		'It is responsible for grabbing an image from the webcam and updating the img field.
		'NOTE:the function grabFrame() below is in an external c file
		
		'since the Draw() method can access the img field, we will lock it while we fill it with data!
		'Only lock it if a Mutex has been set for it! (it acts as a flag in this situation telling whether we are running in a thread or not)
		'This makes it thread-safe while running in multi-threaded, and also work if not running threaded.
		If Self.mux Then LockMutex(Self.mux)
		Self.img=grabFrame()
		If Self.mux Then UnLockMutex(Self.mux)
	End Method
	
	Method Draw(x:Int,y:Int)
		'The Draw() method is only called from the main thread. This way, the main thread can run as fast as possible!
		'Again, only lock if a mutex has been assigned!
		If Self.mux Then LockMutex(Self.mux)
		If Self.img Then DrawImage(Self.img,x,y) 'Just in case img hasn't been filled yet...
		If Self.mux Then UnLockMutex(Self.mux)
	
	End Method
	
	Method SetMutex(this:TMutex)
		'This is called only IF the program is multithreaded! 
		'Call this from the main application after creating a thread and a mutex!
		'By doing so, mux will no longer = Null and it will trigger the Lock/Unlock mutex code in the other methods!
		Self.mux=this
	End Method



End Type




main.bmx:
Include "./TCam.bmx"

Global THREADED=True			'set to true if you want to run threaded, false for single thread
Global updateThread:TThread		'declare the thread
Global imgMutex:TMutex			'declare the mutex
Global cam:TCam=New TCam		'creat a new TCam instance

If THREADED
	'Multi-thread enabled!!
	'Start the update thread to separately grab webcam images!
	updateThread=CreateThread(threadUpdateCam,"")
	imgMutex=CreateMutex()
	cam.SetMutex(imgMutex)
	
EndIf


While Not KeyDown(KEY_ESC)
	Cls
	If Not THREADED
		'We are running in a single thread, so we have to call cam.Update here to grab the images!
		cam.Update()
	End If
	cam.Draw(0,0)
	Flip
Wend

Function threadUpdateCam()
	While True
		cam.Update()
	Wend
End Function



I should also note... Some of you would suggest to make a Create() function for the TCam type that takes the mutex as an argument. This would involve creating the thread before TCam instance exists - which causes a segfault because the threadUpdateCam() function would be working with a non-existant type instance! That is the reason for the separate SetMutex() method, in case anyone was wondering... (Though I guess technically you can create the mutex before the actual thread and make a Create() function that takes the mutex argument, but this is just how I did it...)


Nate the Great(Posted 2010) [#2]
if you could post how to recieve the image from a camera (does it come on a stream?) im sure it would help a lot more people than just talking about the threads. I have been wondering how to get webcam images directly with bmax and not using some other app.


ima747(Posted 2010) [#3]
In your TCam.bmx you are locking the mutex, then doing things, then locking it again, don't you mean to unlock the mutex when you're done with the threaded access?

Additionally there's no way to end your update thread since it's just an infinite loop with no escape. Not really a huge issue I think since it will get killed when the app shuts down, but if it checked a var each cycle rather than just true you'd be able to end it.

Since the thread is un-endable I believe you could detach it and don't need the global to store it since you can't really do anything with it... personal preference.

A mutex can be created before a thread. You could put the THREADED conditional in the "New()" method for the camera type so auto set a mutex on any cameras you create if there were multiple cameras... also wouldn't need the setmutex method then since it would all be internal, but that's more preference than anything else as well :0)

Thanks for helping document threading, it's quite an empty space in the official docs.


jondecker76(Posted 2010) [#4]
ima747: good catch - updated the code above to what I should have done in the first place (I copied the LockMutex from above and forgot to add the "Un")

Thanks for pointing it out!

Re: having no escape in the thread... I had tried:
while threadRunning(MainThread())
...
...
wend

but for some reason it doesn't work. Of course, I should find a way to end the thread correctly, but using a variable to track it seems kludgy at best! (the variable may not even exist if you kill the main thread!)

If anyone has any insight, I'd like to discuss it!