I recently needed to play with threaded workers and after I finished cleaning up some old code I thought it could be made into a template, so here it is.
Just keep in mind that this may not be the most optimal way to go about doing threaded workers, but it helps you navigate around some restrictions of the language and it's easy enough to customize.
Code: Select all
;{- Code Header
; ==- Basic Info -================================
; Name: SynchronousThreadedWorkerTemplate.pb
; Version: 0.0.1
; Authors: Herwin Bozet
; Create date: Friday, 22 May 2020, 13:35:16
;
; Description: N/A
;
; ==- Compatibility -=============================
; Compiler version: PureBasic 5.70 (x64) (Other versions untested)
; Operating system: Windows 10 (Other platforms untested)
;
; ==- License -===================================
; License: Unlicense
;
;}
;- Compiler directives & imports
;{
EnableExplicit
CompilerIf #PB_Compiler_Thread = 0
CompilerError "Required: Thread-safe compiler flag."
CompilerEndIf
;}
;- Constants & Structures
;{
#WORKER_POOL_SIZE_DEFAULT = 64
#WORKER_POST_BIRTH_DELAY = 50 ; ms
; A struct is used to pass more than 1 argument to threads
Structure CustomThreadParameterStruct
CustomMutex.i ; Not sure about the type
; [Custom variables here]
EndStructure
;}
;- Procedures
;{
Procedure CustomWorkerThread(*Parameters.CustomThreadParameterStruct)
;[Pre-lock code]
LockMutex(*Parameters\CustomMutex)
;[Locked code]
UnlockMutex(*Parameters\CustomMutex)
;[Post-lock code]
EndProcedure
;}
;- Code
;{
; These 2 lists could be arrays, but they are annoying to use in PB.
; And the small performance tradeoff is worth it IMHO.
NewList WorkerPoolThreadIDs.i()
NewList WorkerPoolThreadParams.i()
Define CustomMutex = CreateMutex()
Define iWorkerPool.i
Define *TemporaryParamsPtr.CustomThreadParameterStruct
Define WorkerPoolSize = #WORKER_POOL_SIZE_DEFAULT
; Initializing the worker lists...
For iWorkerPool = 0 To WorkerPoolSize - 1
InsertElement(WorkerPoolThreadIDs())
WorkerPoolThreadIDs() = #Null
InsertElement(WorkerPoolThreadParams())
WorkerPoolThreadParams() = #Null
Next
; Spawning the threads...
While #False ; ### Change this to something appropriate for your use case ###
ForEach WorkerPoolThreadIDs()
If Not IsThread(WorkerPoolThreadIDs())
; Cleaning up some garbage (leftovers from previous threads)
SelectElement(WorkerPoolThreadParams(), ListIndex(WorkerPoolThreadIDs()))
If WorkerPoolThreadParams()
FreeMemory(WorkerPoolThreadParams())
EndIf
WorkerPoolThreadParams() = AllocateMemory(SizeOf(CustomThreadParameterStruct))
*TemporaryParamsPtr = WorkerPoolThreadParams()
*TemporaryParamsPtr\CustomMutex = CustomMutex
WorkerPoolThreadIDs() = CreateThread(@CustomWorkerThread(), *TemporaryParamsPtr)
; Staggers the launch of future threads (not required !)
Delay(#WORKER_POST_BIRTH_DELAY)
EndIf
Next
Wend
; Waiting for the last threads to finish...
ForEach WorkerPoolThreadIDs()
If IsThread(WorkerPoolThreadIDs())
WaitThread(WorkerPoolThreadIDs())
EndIf
Next
; Cleaning up...
ForEach WorkerPoolThreadParams()
If WorkerPoolThreadParams()
FreeMemory(WorkerPoolThreadParams())
EndIf
Next
Debug "Done !"
;}