Sep 15 2008
Multithreading in BlitzMax
Finally!
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. SuperStrict '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) Next 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 Next 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 ) Repeat hasCoffee :- Rand(1, Int(cupSize/4) ) If hasCoffee <= 0 SetGadgetText( devStatus[Position], name + " needs coffee !!!") hasCoffee = machine.GiveCoffee( name, Position ) Else Local cupContents:String = "" For Local n:Int = 1 To Int( 1 + hasCoffee/10) cupContents :+ "#" Next 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) myDev.Working() End Function 'Start the coffee machine Global Luigi:CoffeeMachine = New CoffeeMachine Luigi.Create() 'Get the guys to work Global DevTeam:Developer[10] Global devName:String[] = [ "Mark", .. "Brucey", .. "Skidracer", .. "SebHoll", .. "SimonH", .. "SimonA", .. "FlameDuck", .. "MarkT", .. "RobH", .. "Woz"] For Local n:Int = 0 To 9 DevTeam[n] = New Developer DevTeam[n].Create( devName[n], n, Luigi ) Next 'Ready, steady, go! Global handles:Int[10] For Local n:Int = 0 To 9 handles[n]=CreateThread( BuildThread, Object(DevTeam[n]) ) Next 'Send the boys home Function ShutDown() Developer.SignalEOB() For Local n:Int = 0 To 9 DetachThread( handles[n] ) Next End Function 'Your good old fashioned GUI event loop Repeat Select WaitEvent () Case EVENT_GADGETACTION If (EventSource () = goHome) ShutDown() End If Case EVENT_WINDOWCLOSE ShutDown() End Case EVENT_MENUACTION If EventData () = 1 Then ShutDown() End End If If EventData () = 3 Then Notify ("Coffee, Inc. only works with coffee!") End Select Forever
Leave a Reply
You must be logged in to post a comment.