Day 039 - The thread pool example

There's a very brief (one page) discussion of threads on page 381, which basically tells you to bring up the Pool of threads example, which I found quickly by searching the examples (see below). There are also a couple of other interesting threading-related examples. It's really nice to be able to find the examples this way, although I forgot to mention yesterday that I was not able to locate the indirection example using the IDE - I had to go hunting on disk. 

Here's the sample program in action. I've initialized the thread pool to a maximum of five,and I've added eight tasks, each of which will draw a bubble which floats up and disappears. Only five bubbles are visible at a time because there are only five threads in the pool (so I really think the status should be "Waiting" for the last three threads, but that's a minor quibble). 

 

Once each bubble reaches the top its thread is terminated and if a bubble is waiting to be formed, a new thread is created for that task.

There are two main reasons for having a thread pool. You can use it to limit the number of concurrent tasks, when you have more tasks than available threads (e.g. on a web server), and you can use it to maintain a number of available threads when creating a thread consumes a lot of resources (e.g. database connections). 

I spent a little bit of time puzzling over the threading code. Here's the code that executes when your initialize the pool:

// Start from the first thread
gnThreadNum = 1

// Delete the former semaphore (if any)
SemaphoreDestroy("SEM_LIMIT")
 
// Recreate the semaphore
// This is a general semaphore for managing the pool of threads.
// Specify in parameter the maximum number of uses for the semaphore, which means the maximum number of threads
SemaphoreCreate("SEM_LIMIT",SLD_Nb_Thread..Value)
 
// Ungray the button for bubble creation
BTN_New_Bubble..State = Active
 
// Modify the number of threads in the pool
SLD_Nb_Thread..State = Active

The key statement is that SemaphoreCreate() call. The semaphore limits the number of threads that can be active at any one time (determined here by the value you set in the UI). 

When you click the button to create a bubble, the following code executes

HourGlass(True)


// Unable to modify the number of threads in the pool
SLD_Nb_Thread..State = Grayed
 
// Run the thread
ThreadExecute(csThreadName+gnThreadNum,threadNormal,Bubble,gnThreadNum)
 
// Wait for thread to be started
ThreadWaitSignal()
 
// Increment the number of the current thread
gnThreadNum ++
STC_Static1 = "You create bubbles. To erase them and to stop all the corresponding threads, click the ""Stop-Erase"" button"
Multitask(-300)
STC_Static1 = ""
HourGlass(False)

The third parameter to ThreadExecute is the name of the procedure to run, and all parameters after the third parameter are passed to this procedure. Here's the procedure that's called:

PROCEDURE Bubble(nBubbleNum)
nWindowHandle is system int = Handle(WIN_Main)


// Indicate that the thread is loaded and started
ThreadSendSignal(".")


// Use semaphores to check the number of threads at a given time
SemaphoreStart("SEM_LIMIT")


// Initialize the engine for random generation
InitRandom()


// Initialize the positions
nInitXPos is int = (IMG_BCKGRD..Width)/2+Random(0,(IMG_BCKGRD..Width)/2)-Random(0,(IMG_BCKGRD..Width)/2)
nInitYPos is int = IMG_BCKGRD..Height
nPosition is int
nXPos, nYPos are int
nOldXPos, nOldYPos are int


// The bubble moves to the top of the image control
nXPos = nInitXPos
nYPos = nInitYPos
WHILE 1		


	// The bubble moves up
	nOldYPos = nYPos
	nYPos = nYPos - Random(0,2)
	
	// The bubble moves slightly to the side
	nOldXPos = nXPos
	nXPos = nXPos+Random(0,2)-Random(0,2)
	
	// If the bubble is not at the top, let's draw it, otherwise it bursts
	IF nYPos>0 AND nXPos>0 AND nXPos<IMG_BCKGRD..Width THEN	
		// Draw the bubble
		Message("Draw the bubble "+nBubbleNum+"; nPosX: "+nXPos+" , nPosY : "+nYPos)
		PostMessage(nWindowHandle, "CLEAR",nOldXPos,nOldYPos)
		nPosition = MakeInteger(nXPos,nYPos)
		PostMessage(nWindowHandle, "DRAW",nPosition, nBubbleNum)
	ELSE	
		// The bubble bursts
		Message("")
		
		PostMessage(nWindowHandle, "CLEAR",nOldXPos,nOldYPos)	
		
		// Free a semaphore unit
		SemaphoreEnd("SEM_LIMIT",1)
		BREAK		
	END	
	
	ThreadPause(1)
END

One thing I noticed when running the example is that although the pool is limited to, say, five threads, I can immediately add more than five bubbles. At the top of the Bubble procedure you can see a call to ThreadSendSignal(). The code that starts the Bubble procedure on its own thread is waiting for that signal, and when it comes that code (which is on the UI thread) continues, so there's no delay. But the very next line after ThreadSendSignal (in the bubble drawing thread) is SemaphoreStart("SEM_LIMIT"), which blocks the thread if the semaphore's thread count is currently at the maximum defined in the call to SemaphoreCreate(). 

I may have missed something in the code, but it seems to me that the order in which the threads execute is undefined (or undefinable). If I load up the queue with a bunch of bubbles waiting to be created, they show up in seemingly random order after the first set have gone by.

If you need threads to be serviced in the order in which they are created, you may need to use a different approach. But it's still pretty nice to be able to easily queue up a bunch of threads and have them execute as resources become available.