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.Â
Â