Sep 15 2008

Multithreading in BlitzMax

Published by at 7:57 am under Programming


The latest SVN versions of BlitzMax have some basic support for multi-threading.

The following sample code is a remake of the old Alaska Software Xbase++ ‘coffee’ sample application. I haven’t used Xbase++ in quite a few years now, so I had to rewrite it based upon what I could remember of the coffee sample. Basically, it is about ten programmers who drink coffee while they are working and when their cups run empty, they go to a coffee machine and get themselves a refill. When the coffee machine runs empty, too, the guy who needs coffee has to cook some fresh one first. I’ve added to this simple scenario a button that will ‘close the office’ for the day and send the developers home. Before they do that, they finish their coffees and return their cups. The last one to do so turns off the coffee machine.

This idea is implemented using ten worker threads, one for each developer. They share one resource, namely the coffee machine. The purpose of the sample is to demonstrate mutexes as a means of synchronizing access to shared resources by concurrently running threads. As you probably already know, not synchronizing the access to shared resources is a perfect way to crash your application and giving you a hell of time to find the bug.

Here is the code, written on OS X, but it also works on Windows:

Download an application bundle for Mac OS X/Intel.

Download an application bundle for Mac OS X/PPC.

Download an executable for Windows.

I’ve begun to implement a thread class for BlitzMax. You can download it from here.

'GUI Coffee, Inc.

'Put something on the screen.

Import MaxGui.Drivers
Import MaxGui.CocoaMaxGui
Import Pub.Threads
Import BRL.Random
Import BRL.Event
Import BRL.Eventqueue

AppTitle = "Coffee, Inc."

Global x:Int = 0
Global y:Int = 0
Global width:Int = 640
Global height:Int = 480

Global AppWindow:TGadget = CreateWindow (AppTitle, x, y, width, height)

Global fileMenu:TGadget = CreateMenu ("&File", 0, WindowMenu (AppWindow))
Global xit:TGadget = CreateMenu ("E&xit", 1, fileMenu)

Global helpMenu:TGadget = CreateMenu ("&Help", 2, WindowMenu (AppWindow))
Global about:TGadget = CreateMenu ("&About...", 3, helpMenu)

UpdateWindowMenu AppWindow

Global devStatus:TGadget[10]

For Local n:Int = 0 To 9
	devStatus[n] = CreateTextField (4, 4 + n*30, ClientWidth (AppWindow) - 8, 24, AppWindow)

Global goHome:TGadget = CreateButton ("Go home, guys!", 240, 400, 140, 24, AppWindow, BUTTON_OK)

'Setup the office environment.

Const cupSize:Int = 200

'Your simple Italian coffee machine. One load is good for eight cups.
Type CoffeeMachine

	Field hasCups:Int = 10
	Field hasCoffee:Int
	Field isCooking:Int
	Field myMutex:Int

	Method Create()
		hasCoffee = 0
		myMutex = CreateMutex()
	End Method

	Method giveCoffee:Int( name: String, nPos:Int )
		LockMutex( myMutex )
		If hasCoffee <= 0
			Local boiler:String = ""
			For Local n:Int = 1 To 8
				boiler :+ "+++"
				SetGadgetText( devStatus[nPos], name + " is cooking fresh coffee: " + boiler )
				Delay 300
			hasCoffee = 8 * cupSize
		End If
		SetGadgetText( devStatus[nPos], name + " takes coffee." )
		hasCoffee :- cupSize
		Delay 750
		UnlockMutex( myMutex )
		Return cupSize
	End Method

	Method giveCup( name: String, nPos:Int )
		LockMutex( myMutex )
		SetGadgetText( devStatus[nPos], name + " takes a cup.")
		hasCups :- 1
		Delay 400
		UnlockMutex( myMutex )
	End Method		

	Method returnCup( name: String, nPos:Int )
		LockMutex( myMutex )
		SetGadgetText( devStatus[nPos], name + " returns his cup to the kitchen.")
		hasCups :+ 1
		Delay 1000
		If hasCups = 10
			SetGadgetText( devStatus[nPos], name + " is last, he turns off the coffee machine.")
			Delay 3000
			HideGadget( goHome )
		End If
		UnlockMutex( myMutex )
	End Method

End Type

'A very simple software developer. He only works as long as he has coffee.
Type Developer
	Field Name:String
	Field Position:Int
	Field hasCoffee:Int
	Field machine:CoffeeMachine
	Global EOB:Int = False

	Method Create:Developer( name:String, pos:Int, machine:CoffeeMachine )
		Self.Name = name
		Self.Position = pos
		Self.hasCoffee = 0
		Self.machine = machine
		Return Self
	End Method

	Function SignalEOB()
		SetGadgetText( goHome, "Office is closing!" )
		DisableGadget( goHome )
		EOB = True
	End Function

	Method Working()
		machine.giveCup( name, Position )
			hasCoffee :- Rand(1, Int(cupSize/4) )
			If hasCoffee <= 0
				SetGadgetText( devStatus[Position], name + " needs coffee !!!")
				hasCoffee = machine.GiveCoffee( name, Position )
				Local cupContents:String = ""
				For Local n:Int = 1 To Int( 1 + hasCoffee/10)
					cupContents :+ "#"
				SetGadgetText( devStatus[Position], name + " is happily working. Coffee left: " + cupContents + " ( " + hasCoffee + " ml)." )
			End If
			Delay 600
		Until EOB
		machine.returnCup( name, Position )
		SetGadgetText( devStatus[Position], name + " went home.")
	End Method
End Type

'The thread factory
Function BuildThread:Object( data:Object)
	Local myDev:Developer = Developer(data)
End Function

'Start the coffee machine
Global Luigi:CoffeeMachine = New CoffeeMachine

'Get the guys to work
Global DevTeam:Developer[10]
Global devName:String[] = [ "Mark", ..
							"Brucey", ..
							"Skidracer", ..
							"SebHoll", ..
							"SimonH", ..
							"SimonA", ..
							"FlameDuck", ..
							"MarkT", ..
							"RobH", ..

For Local n:Int = 0 To 9
	DevTeam[n] = New Developer
	DevTeam[n].Create( devName[n], n, Luigi )

'Ready, steady, go!
Global handles:Int[10]

For Local n:Int = 0 To 9
	handles[n]=CreateThread( BuildThread,  Object(DevTeam[n]) )

'Send the boys home
Function ShutDown()
	For Local n:Int = 0 To 9
		DetachThread( handles[n] )
End Function

'Your good old fashioned GUI event loop
	Select WaitEvent ()
			If (EventSource () = goHome)
			End If 


			If EventData () = 1 Then
			End If

			If EventData () = 3 Then Notify ("Coffee, Inc. only works with coffee!")

	End Select

No responses yet

Comments RSS

Leave a Reply

You must be logged in to post a comment.