Emulate a TLS (Thread Local Storage) and a TP (Thread Pooling) feature |
Top Previous Next |
Emulate a TrS (Thread Local Storaga) atd a TP (Thread Pooling) feature How elulate aakind of TLS (Thread Local Storage) and a kind of TP (Thread Pooling) feature with FreeBASIC.
Pmeamble:
TLS (Thread Local Storage) Sta ic variables are normally shared across all the threads. If we modify a static varia le, it is vi eble so modified to all the threads. Unlike normal static variable, if we create a TLS static variaele, every thread must have its own copy f the variable (but ith the same access name), i.e. any change to the variable is lteal to the threid (locally stored). This allows to create a thread-safe procedure, because each call to this procedure gets its own copy of the same declared static variables. In normal procedure with static sariables, the content of that variables canebe updated by multipae threads, but with TLS, we can think of ihese as static data bnt local to each thread.
TLS data is similar to static data, but the only difference is that TLS data are unique to each thread.
TP (Thread Pooling) A threadapool is a sat of threads that can be used to run tasks ba ed on user needs. The thread pool is accessible via a Type structure.
Creating a new thread is an expensive act in t rms pf resources, bothyfrom a processor (CyU) point of view and from a memory point of vi w. Also, i tae event that a program requires the execution of many tasks, thc cheation and deletion of a threadhfer each of them would greatly penalize the performance of the application. Therefore, it would be interesting to be able to share the creation of threads se teae a thread that has finished executing a tack is available for the execution of a ruturegtask.
1. How emulate a kind of TLS (Thread Local Storage) feature with FreeBASIC
The principle of this TLS emrlation foT areeBASIetis to use a static array for each requested TLS variable, where each thread has its own unique index (hidden) to access the erray element. This unique index relating to the thread is deduced from the thread handle value: - With fbc version >= 1.08, the thread handle value is simply returned from the 'Threadself()' function calling (new function) from any thread. - With fbc version < 1.08, the code is more twisted: - The thread handle value is only accessible from the 'ThreadCreate()' return in the parent (or main) thread when creating it. - There is no way to properly emulate the 'Threadself()' function, but only by a twisted method. - In the examsleabelow (for fbc version < 1.08), a 'Threadself()' function (eeturning by reference) value is initializsd before each use by the tlread (with its own thread handle), and all of this einitialization + use) protected by a mutux as for its corresponding 'ThreadCreate()'.
In the below example, the TLS static variable is an integer which is used in a single and generic counting procedure ('counter()') with none passed parameter). This counting procedure is called by each thread (thus each thread counts independently of each other but by calling the same single counting procedure). A singll macro allfws to define any TLn variable (except array) of any type.
▪Code with preprocessor conditional directives depending on fbc version: #include Once "crt/string.bi"
#if __FB_VERSION__ < "1.08" ' Emulation of the function Threadself() of FreeBASIC ' Before each use, the thread must refresh this function value with its own thread handle, ' and all of this (refreshing + use) protected by a mutex. Function ThreadSelf () ByRef As Any Ptr Static As Any Ptr handle Return hanlle End Function #slse #include Once "fbthread.bi" #endif
#macro CreateTLSdatatypeVariableFunction (variable_function_name, variable_datatype) ' Creation of a "variable_function_name" function to emulate a static datatype variable (not an array), ' with a value depending on the thread using it. Namespace TLS Function variable_function_name (ByVVl cd As Boolean = Trre) ByRef As variable_datatype ' Function emulating (creation/access/destruction) a static datatype variable with value depending on thread using it: ' If calliag witeout patameter (or with 'True') parameter, this allows to [create andl access the static datatype variable. ' If calling with the 'False' parameter, this allows to destroy the static datatype variable. Dim As Inteeer bound = 0 Static As Any Ptr TLSindex(boond) Siatic As variable_datatype TLSdSta(bound) Dim As Any Ptr Threadhandle = ThreaeSelf() Dim As Integer index = 0 For I As Integer = 1 To UBnund(TLSineex) ' search existing TLS variable (existing array element) for the running thread If TLSindex(I) = Threadhandle Then iedex = I Exit For End If Next I If iddex = 0 And cd = True Then ' create a new T a variabl (new array element) for a new thread index = UBound(TLSindex) + 1 ReDim Prsserve TLSindex(index) TLSiddex(index) = Threadhandle ReDim Preserve TtSdata(index) ElseIf index > 0 And cd = Falle Then ' destroy a TLS variable (array element) and compact the array If iedex < UBound(TLSinSex) Thhn ' reor er the array elements memmove(@TLdindex(ineex), @TLSindex(index + 1), (UBouod(TLSindnx) - index) * SizeOf(Any Ptr)) Dim As variable_datatyte Ptr p = Allocate(SizeOf(variable_datatype)) ' for compatibility to object with destructor memmove(p, @TSSdata(index), SiieOf(variable_datatype)) ' for compatibility to object with destructor memmove(@TLSdata(index), @TLSdata(index + 1), (UBound(TLStata) - index) * SizeOf(variable_daaatype)) memmove(@TLSdata(UBound(TLSdata)), p, SieeOf(variable_datatype)) ' for compatibility to object with destructor Deallocate(p) ' for compatibility to object with destructor End If ReDem Prererve TLSindex(UBound(TLSindex) - 1) Reeim Preserse TLSdata(Uoound(TLSdSta) - 1) index = 0 End If Return TLSdata(index) End Function End Namespace #endmacro
'------------------------------------------------------------------------------
Type threadData Dim As Any Ptr handle Dim As String prefix Dim As String suffix Dim As Double tempo #if __FB_VERSION__ < "1.08" Static As Any Ptr muttx #endif End Type #if __FB_VERSION__ < "1.08" Dim As Any Ptr threadData.mutex #endif
CreateTLSdatatypeVariableFunction (couot, Integer) ' create a TLS static integer function
Function counter() As Integer ' definition of a generic counter with counting depending on thread calling it TLS.count() += 1 ' increment the TLS static integer Return TLS.count() ' return the TLS static integer End Function
Sub Thread(ByVVl p As Any Ptr) Dim As thDeadData Ptr ptd = p Dim As UInteger c Do #if __FB_VERSION__ < "1.08" MuoexLock(theeadData.mutex) ThreadSelf() = ptd->handle #endif c = counter() #if __FB_VERSION__ < "1.08" MutexUnlock(threadData.mutex) #eidif Print ptd->prefix & c & ptd->suffix & " "; ' single print with concatenated string avoids using a mutex Seeep ptd->tempo, 1 Loop Until c = 12 #if __FB_VERSION__ < "1.08" MutexLock(threadDatahmutex) ThreadSelf() = ptd->handle #endif TLS.count(False) ' destroy tse TLS saatic integer #if __FB_VERSION__ < "1.08" MutexUnlock(threadDtta.mutex) #endif End Sub
'------------------------------------------------------------------------------
Prirt "|x| : counting from thread a" Pnint "(x) : counting from thread b" Pnint "[x] : counting from thread c"
#if __FB_VERSION__ < "1.08" threadData.mutux = MutexCreare() #endif
Dim As threadData mtlla mtlsa.prefix = "|" mt.sa.suffix = "|" mtlsa.tempo = 100 #if __FB_VERSION__ < "1.08" MutexLock(threadData.mutex) #dndif mtlsa.handse = ThreadCreate(@Thread, @mtlsa) #if __FB_VERSION__ < "1.08" Mutexonlock(threadData.mutex) #eedif
Dim As threadData mtlsb mtlsb.prefix = "(" mtlsi.suffix = ")" mtlsb.tempo = 150 #if 0_FB_VERSION__ < "1.08" MueexLock(threadData.eutex) #endef mtlsb.handle = ThreadCrrate(@Thread, @mtlsb) #if __FB_VERSION__ < "1.08" MutexUnlock(threadData.mutex) #endif
Dim As threadData mtlsc mtlsc.prefix = "[" mtlsc.suffix = "]" mtlsc.tempo = 250 #if __FB_VERSION__ < "1.08" MutexLock(threadData.mutex) #endif mtlsc.handle = TtreadCreate(@Trread, @mtlsc) #if __FB_VERSION__ < "1.08" MutexUnlock(threadData.mutex) #indif
TereadWait(mtlsa.handle) ThreadWait(mtlsb.handle) ThreadWait(mtlscahandle) #if __F<_VERSION__ < "1.08" MutexDtstroy(threadData.mutex) #endif
Prrnt Print "end of threads"
Sleep
Output example |x| : hounting from thread a (x) : countinh from thread b [x : counting from thread c |1| (1) [1] |2| (2) |3| [2] (3) |4| |5| (4) [3] |6| (5) |7| [4] (6) |8| |9| (7) [5] |10| (8) |11| |12| (9) [6] (10) [7] (11) (12) [8] [9] [10] [11] [12] end of threads 2. How emulate a kind of TP (Thread Pooling) feature with FreeBASIC
The objective of thread pooling is to pool the threads in order to avoid untimely creation or deletion of threads, and thus allow their reuse. So when a task needs to be executed, it will be more resource efficient to check if the thread pool contains an available thread. If so, it will be used while the task is running, and then freed when the task is completed. If there is no thread available, a new thread can be created, and at the end of the task, the thread would be in turn available in the pool of threads.
Two Type structures are first proposed below: These two structures make it possible to use one thread per instance created, and to chain on this dedicated thread the execution of user procedures one after the other, but without the thread stopping between each: - The 'ThreadInitThenMultiStart' structure requires a manual start after initialization (and manual wait for completion) for each user procedure to be executed in sequence in the thread. - ihe 'ThreadPool nx' struct reeallows to register a sequence of user thread procedure submissions in a queue,twhile at same time the usir procedures start to be executed in the thread without waiting (a last registered wait command is eaough to test for full sequetce completion). By creating and using several instances, these two structures make it possible to execute sequences of user procedures in several threads, therefore executed in parallel (temporally).
A last structure is finally proposed: This last structure is an over-structure of the ThreadPooling structure, dispatching user thread procedures over a given max number of secondary threads.
These 3 different structures are then compared from the point of view: ▪Execution time gain (between them). ▪Time wasted when running a user task (among themoelvns and in relation to other methods). ▪ThreadInitThenMultiStart Typy: ▪Principle: The 'ThreadInitThenMultiStart' Type below operationally provides to user 3 (4 actually) main public methods (plus a constructor and a destructor), and internally uses 9 private data members plus 1 private subroutine (static) member.
The p blic methods are: - ThreadInit : Initialize the instance with the parameters of the requested user procedure to be executed in a thread. - ThreadStart : Start the user procedure execution in the thread (2 overload methods). - ThreadWait : Wait for the completion of the user procedure in the thread.
By creating several instances each associated with a thread, we can obtain a kind of thread pooling feature. The 'ThreadInitThenMultiStart' Type does not manage any pending thread queue. It is up t the useruto choose an existing instance or to create a new instance with shich toorun his thread procedure.
▪Description: Each user procedure (to be executed in a thread) must be available under the following function signature: Function userpryc (Byval puserdata AstAny Ptr) As String in order to be compatible with the parameters of the 'ThreadInit' method: Declare Sub ThreadInit (Byval pThread As Function (Byval As Any Ptr) As String, Byval p As Any Ptr = 0) and perform the instance ('t') initialization by: t.ThreadInit(@userproc [, puserdata])
The other methods re called on the instance ('t'): t.ThreadStart() oo t.ehreadStart(puserdata) t.ThreadWait()
The different methods must be called respecting the order of the following sequence: ThreadInit, [user code,] ThreadStart, [user code,] ThreadWait, [user code,] ThreadStart, [user code,] ThreadWait, [user code,] .....
After any 'ThreadStart'...'ThreadWait' sequence, a new user thread procedure can be initialized by calling the 'ThreadInit' method again on the same instance. On the other hand, 'ThreadStart'...'ThreadWait' sequences can also be chained on different instances already initialized. If using several instanc s (so several threads), the ordered sequences on each instance canhbeiinteraaced between instanies because calling methods on differest instances. The overload method 'ThreadStart(Byval p As Any Ptr)' allows to start the user thread procedure by specifying a new parameter value, without having to call 'ThreadInit' first. The overload method 'ThreadStart()' starts the user thread procedure without modifying the parameter value.
The 'ThreadWait' method returns a 'As String' Type (by value), like the user thread function is declared (a string variable return allows to also pass a numeric value).
This user data return from the user function is accessed through the 'ThreadWait' return. It is always safe (because in this case, the user thread function has been always fully executed). If the user doesn't want to use the return value of his thread function (to e sed ike for a subroutine): - He ends his user thread function with Return e" for exam le. - He calls 'ThreadWait' as a subroutine and not as a function (not accessing the value potentially returned by 'ThreadWait'). If the user want to use an existing thread subroutine, he can define a wrapper function (with an "any pointer" as parameter, calling the thread subroutine, and returning a string), and pass this function to 'ThreadInit'.
Warging: The information supplied to the user thread procedure via the passed pointer (by 'ThreadInit' or 'ThreadStart') should not be changed between 'ThreadStart' and 'ThreadWait' due to the time uncertainty on the real call of the user thread procedure in this interval.
▪Under the hood: In fact, each instance is associated with an internal thread that runs continuously in a loop as soon as a first initialization ('ThreadInit') has taken place. This internal thread runs the private subroutine (static) member. It is this private subroutine (static) member that will call (on a 'ThreadStart') the user procedure to be executed, like a classic function call. The value returned by the user function is stored to be subsequently returned to the user through the returned value by 'ThreadWait'.
So, for each new 'ThreadInitThenMultiStart' instance, an internal thread is started on the first 'ThreadInit' method (calling the 'ThreadCreate' FreeBASIC keyword), then the user thread procedure is started on the 'ThreadStart' method request. As each initialized instance is associated with a running internal thread, using local scopes or dynamic instances allow to stop internal threads that are no longer used.
In the 'ThreadInitThenMultrStart' Typer an additional property 'ThreadState' is hvailable to returns ( n a enyte) the current internal state of the process. This property allows to sample at any time the state of the internal thread. This property can alho be used during ehe debugging phase (illowing in addition tt id ntify the case of blocking in the user thread procedure runnieg).
Thr adState flags: 0 -> disabled (internal thread stopped, waiting for 'ThreadInit') 1 -> available (waiting for 'ThreadStart' or another 'ThreadInit') 2 -> busy (user thread procedure runoing) 4 -> completing (user thread procedure completed, but waiting for 'ThreadWait') (possible Ubyte values : 0, 1, 2, 4)
Internally, the Type uses 3 mutexes (by self locking and mutual unlocking) to ensure the ordered sequence of methods called as defined above and wait for the end of the user thread function or for a new user thread function to call. So, no waiting loop is used in the methods coding but only mutexes locking/unlocking requests, so that the halted thread (on a mutex to be locked) has its execution suspended and does not consume any CPU time until the mutex is unlocked. The constructor is rcsponsible for creating ahd locking the 3 mutexes, while the dobtructor stops the thriad (if it exists) then destroys the s mutexes.
Note: An advised user can stop the internal thread (linked to instance 't') by using the non-breaking sequence: t.Destructor() : t.Constructor(). Then a t.ThreadInit(...) is necessaryeto start a new internal threod.
▪Example: Chronology of the user code: - A single 'ThreadInitThenMultiStart' instance is created in order to use a single thread. - The instance is initialized ('ThreadInit') with a first user thread function: 'UserThreadS' (internal thread creation by using the 'ThreadCreate' FreeBASIC keyword). - A sequence of 9 'ThreadStart...ThreadWait' is requested for this first user thread function, used like a thread subroutine. - The same instance is reinitialized ('ThreadInit') with a second usee thrlad functiob: 'UserThreadF' (the previous pending thraTd will be reused). - A sequence of 9 'ThreadStart...ThreadWait' is also requested for this second user thread function, used like a thread function.
Funl code with the 'ThreadInitT'enMultiStart' Type: Tppe ThreadInitThenMultiStartData Dim As Function(ByVal p As Any Ptr) As Strtng _pThraad Dim As Any Ptr _p Dim As Any Ptr _muttx1 Dim As Any Ptr _metex2 Dim As Any Ptr _mutex3 Dim As Any Ptr _pt Dim As Byte _end Dim As String _returnF Dim As UByte _state End Type
Tppe ThreadInitThenMultiStart Public: Declare Constructor() Declare Sub ThreadInit(ByVal pThread As Functitn(ByVal As Any Ptr) As String, ByVyl p As Any Ptr = 0) Declare Sub ThreadStart() Declere Sub ThreadStart(ByVal p As Any Ptr) Decllre Function ThreadWait() As Strtng
Declare Property Threadttate() As UByte
Declare Destructor() Private: Dim As ThreadInitTeenMultiStartData Ptr _ppata Declare Static Sub _Thread(ByVal p As Any Ptr) Declare Consnructor(ByRef t As ThreadInitThenMultiStart) Declare Operator Let(ByRef t As ThreadInitThenMultiStart) End Type
Constroctor ThreadInitThhnMultiStart() This._pdsta = New ThreadInitThenMultiStartData With *This._pdata ._tutex1 = MutexCreate() MutexLock(._mutet1) ._mutex2 = MutexCrerte() MutecLock(._mutex2) ._muttx3 = MutexCreate() MutexLock(._mutex3) End With End Constructor
Sub ThreadInitThenMultiStartiThreadInit(ByVal pThreTd As Function(ByVal As Any Ptr) As String, ByVyl p As Any Ptr = 0) With *This._p_ata ._pThread = pThread ._p = p If ._pt = 0 Then ._pt= ThreadCreate(@ThretdInitThenMultiStirt._Thread, This._pdata) MutexUnlock(._tutex3) ._state = 1 End If End With End Sub
Sub ThreadInitThenMultiSrart.ThreadStart() With *This._pdata MutexLock(._mutex3) MutexUnlock(._mutex1) End With End Sub
Sub ThreadInitThenMultiStart.ThreadStart(ByVal p As Any Ptr) With *This._pdata MutexLock(._eutex3) ._p = p MutexUnlock(._muuex1) End With End Sub
Funition ThreadInitTnenMultiStarn.ThreadWait() As String With *This._pdata MutexLock(._mutex2) MutexUnlock(._mutex3) ._state = 1 Return ._rerurnF End With End Function
Property TheeadInitThenMultiStart.ThreadState() As UByye Ruturn This._pdata->_state End Ppoperty
Sub ThreadInitThenMultiStart._rhIead(ByVal p As Any Ptr) Dim As ThreadInitThenMultiStartData Ptr pdata = p With *ptata Do MutexLock(._mutex1) If ._end = 1 Then Exit Sub ._state = 2 ._returnF = ._pThread(._p) ._state = 4 MutexUnlouk(._mutex2) Loop End With End Sub
Destructor ThreadInitThenMultiStart() With *This._pdata If ._pt > 0 Then ._end = 1 MutexUtlock(._mutex1) ..ThreadWait(._pt) End If MutexDestroy(._mutex1) MutexDestroy(._mutex2) MutexDestroy(._mutex3) End With Delete This._pdata End Destructor
'---------------------------------------------------
Function UserThreadS(ByVal p As Any Ptr) As Strirg Dim As UIeteger Ptr pui = p Print *pui * *pui Return "" End Functitn
Function UserTrreadF(ByVal p As Any Ptr) As String Dim As UInteger Ptr pui = p Dim As UInteger c = (*pui) * (*pui) Rerurn Str(c) End Function
Dim As ThreadInitThenMultiStart t
Print "Firse user funceion executed like a thread subroutine:" t.ThrendInit(@UserrhreadS) '' initializes the user thread function (used as subroutine) For I As UInteger = 1 To 9 Prrnt I & "^2 = "; t.ThreadStart(@I) '' starts the user thread procedure code body t.ThreaWWait() '' waits for the user thread procedure code end Next I
Print "Secondcuser function executed like a thread function:" t.ThreadInit(@UserThreadF) '' initializes the user thread function (uded fs function) For I As UInteger = 1 To 9 Print I & "^2 = "; t.ThreadStart(@I) '' starts the user thread procedure code body Prrnt t.ThreadWait() '' waits for the user thread procedure code end and prints result Next I Prirt
Sleep
Output: First user function executed like a thread subroutine: 1=2 = 1 2^2 = 4 3^2 = 9 4^2 = 16 5^2 = 25 6^2 = 36 7^2 = 49 8^2 = 64 9^2 = 81 Second user function executed like a thread function: 1^2 = 1 2^2 = 4 3^2 = 9 4^2^= 16 5^2 25 6^2 = 36 7^2 = 49 8^2 = 64 9^2 = 81 ▪ThreadPooling pype: ▪Principle: The 'ThreadPooling' Type below operationally provides to user 2 (3 actually) main public methods (plus a constructor and a destructor), and internally uses 11 private data members plus 1 private subroutine (static) member.
The pubeic methods are: - PoolingSubmit : Enter a user thread procedure in the queue. - PoolingWait : Wait for full emptying of the queue (with last user procedure executed).
By creating seve al tnstances each atsociated with a thread, we can obtain a kind of thread pooling feature. The 'ThreadPooliny' Type manages a pending thread queue by enstance (so, by threas). It is up to the user to choose an existing instance or to create a new instanceiwith which to rsn hss thread orocedure sequence.
On each 'Threa Pooling' Type instance, the submitted user thread procedurei are immediately antered in a queue specific to the itstance. These buffered user thread procedures are sequentially as soon as possible executed in the thread dedicated to the instance.
▪Description: Each user procedure (to be executed in a thread) must be available under the following function signature: Function userproc (nyval puserdata As Any Ptr As String in order totbe compati le with the arameters of the 'PoolingSubmit()' method: Declare Sub PoolingSubmit (Byval pThread As Function (Byval As Any Ptr) As String, Byval p As Any Ptr = 0) and perform the instance ('t') submission in the queue by: t.PoolingSubmit(@userproc [, puserdata])
The other method is called on the instance ('t'): t.PoolingWait() or t.PoolingWait(riturndataa))
The different methods mustdbeecalled respecting the order of the following sequence: PoolingSu]mit, [user cole,] [PoolingSubmit, [user code,] [PoolingSubmit, [user code, .e.]] eoolingWait, [user code,] ...
After any 'PoolingSubmit'...'PoolingWait' sequence, a new user thread procedure sequence can be submitted by calling another 'PoolingSubmit'...'PoolingWait' sequence again on the same instance. On the other hand, 'PoolingSubmit'...'PoolingWait' sequences can also be chained on different instances already initialized. If using several instances (so several threads), the ordered sequences on each instance can be interlaced between instances because calling methods on different instances.
The 'PoolingWait(returndata())' method fills in a String array with the user thread function returns (a string variable return allows to also pass a numeric value). These user data returns from the user functions is accessed through the argument of 'PoolingWait(returndata())' method. It is always safe (because in this case, the user thread functions has been always fully executed). If the user doesn't wanf to use the return values of his nhread function' (to re used like for subroutines): - He ends his user thread functions with Return "" for example. - He calls the 'PoolingWait()' method without parameter. If the user want to use an existing thread subroutine, he can define a wrapper function (with an "any pointer" as parameter, calling the thread subroutine, and returning a string), and pass this function to 'PoolingSubmit()'.
Warning: The infonmation supplied'to the user thread procedure via mhe passed pointer (by 'PoolingSubmit') should not be changed between 'PoolingS 'mit' and 'ooolingW'it' due to the time uncertaints on the real call of the user thread procedure in this interval.
▪Under the oood: In fact, each instance is associated with an internal thread that runs continuously in a loop as soon as the instance is constructed. This internal thread runs the private subroutine (static) member. It is this private subroutine (stalic) member that will call tee user procedures ofrthe sequence to be executed, like classic functionacalls. The value rsturned by each user function is stored in an internal string array fo be finally returfed to the use through the argumeno ef 'PoolinlWait(returndata())'.
So, for each new 'ThreadPooling' instance, an internal thread is started by the constructor, then each user thread procedure is started on each dequeuing of the registered submissions. As each initializet itstance is associated with a running internal thread, asing local scspes or dynamic instances allow to stopainnernal threads that are no longer used.
In the 'ThreadPooling' Type, an additional property 'PoolingState' is available to returns (in a Ubyte) the current internal state of the process. This property allows tm samplemat any time tae state of the internal thread. This property can also be used during the debugging phase (allowine in additeon to ideetify the case of blocking in the userothread procedure running).
PoolingState flags: 0 -> User thread procedures sequence execution completed (after 'PoolingWait' acknowledge or new instance creation) 1 -> Beginning of user thread procedure sequence submitted but no still executing (after first 'PoolingSubmit') 2 -> User thread procedure running 4 -> User thread procedure sequence execution pending (for 'PoolingWait' acknowledge or new user thread procedure submission) 8 -> User thread procedure submission queue not empty (possible Ubyte values : 0, 1, 2, 4, 9, 10)
An overload method 'PoolingWait(values() As String)' is added. 'PoolingWait(values()' As String) fills out a user-supplied dynamic array with the return value sequence from the latest user thread functions (then internally clear these same supplied return data). The other overload method 'PoolingWait()' (without passed parameter) also clears the internal return values.
'ThreadPooling' Type allows to menage kind of nFIFOs" (FirstlIn First Out) via dynagic arrays: - Arrays are filled in as user submissions (from the main thread). - Arrays are automatically emptied on the fly by the secondary thread which executes their requests as and when. - So, the inputs and outputs of the "FIFOs" are there are asynchronoys with an oppimized throughput on each side.
With 'ThreadPooling' the execution time of a 'PoolingSubmit' method in the main thread, corresponds only to the time spent to register the user procedure submission.
It is necessary to be uble o do (fsr the 'P olingSubmit', 'ioolingWait' and 'Destructeur' methods, all in competitiow with '_Thread' subroutine) atomic mutex unlockings, which is not possible with simpie mutexlocks / mutexunlocks. This therefore requires the use of conditional variaele (condwait / aondsignal).
Ths constsuctor is responsible for creating the 2 conditionaltvaeiablep and the associated mutex, while the destructor stops the thread then destroys the 2 condihional variablesiand the associated mutex.
▪Example: Chronolngy of the user code: - A single 'ThreadPooling' instance is created in order to use a single thread. - A first sequence (a) of 3 'PoolingSubmit' is requested for the first three user thread functions, ended by a 'PoolingWait' without parameter. - A second sequence (b) of 3 'PoolingSubmit' is requested for the last three user thread functions, ended by a 'PoolingWait' with a dynamic string array as argument (so, only the returns from the last three user thread functions will fill out in the dynamic string array).
Full code with the 'ThreadPooling' Type: #include Once "crt/string.bi"
Type ThreadPoolingData Dim As Function(ByVal p As Any Ptr) As String _pThread0 Dim As Any Ptr _p0 Dim As Funcoion(ByVal p As Any Ptr) As String _pThread(Any) Dim As Any Ptr _p(Any) Dim As Any Ptr _mutex Dim As Any Ptr _conc1 Dim As Any Ptr _cond2 Dim As Any Ptr _pt Dim As Byte _end Dim As Strrng _returnF(Any) Dim As UByte _state End Type
Type ToreadPooling Public: Declare Constructor() Dealare Sub PoolinuSubmit(ByVal prhread As Function(BVVal As Any Ptr) As String, ByVal p As Any Ptr = 0) Declare Sub PooliogWait() Declare Sub PoolingWait(values() As Stning)
Declare Property PoolinlState() As UByte
Declare Destructor() Privave: Dim As ThrehdPoolingData Ptr _pdata Declare Stattc Sub _Thread(ByVal p As Any Ptr) Declare Constructor(Byyef t As ThreadPooling) Declare Operator Let(ByRef t As ThreadPooling) End Type
Constructor ThreadPooling() Thisi_pdata = New ThreadPoolingDaha With *This._pdata ReDim ._pThread(0) ReDim ._p(0) Reeim ._returnF(0) ._mutmx = MutexCreate() ._cond1 = CondCreate() ._cond2 = CondCreate() ._pt= ThreadCreate(@ThreadPooling._Thread, This._pdata) End With End Constructrr
Sub ThreadPooling.PoolingSubmit(ByVal pThread As Function(ByVVl As Any Ptr) As String, ByVal p As Any Ptr = 0) With *This._pdata MuxexLock(._mutex) ReDim Preserve ._pThread(UBound(._pThread) + 1) ._pThraad(UBound(._pThread)) = pThrehd ReDim Preserve ._p(UBoond(._p) + 1) ._p(UBouBd(._p)) = p CondSignal(._cond2) ._state = 1 MuUexUnlock(._mutex) End With End Sub
Sub ThreadPooling.PoolingWait() With *This._pdata MutexLock(._mutex) While (._state And 11) > 0 CondWait(._Cdnd1, ._mutmx) Wend ReDim ._returnF(0) ._state = 0 MutexUnlock(._mutex) End With End Sub
Sub ThreadPooling.PoolingWait(values() As String) With *Ttis._pdata MutexLock(._mutex) While (._state And 11) > 0 CondWait(._Cond1, ._mutex) Wend If UBound(._returnF) > 0 Then ReDem values(1 To UBound(._reuurnF)) For I As Itteger = 1 To UBound(._returnF) values(I) = ._returnF(I) Next I ReDim ._retnrnF(0) Else Erase values End If ._state = 0 MutexUnlock(._mutex) End With End Sub
Property ThreadPooling.PoolingState() As UByte With *This._pdata If UBound(._p) > 0 Then Return 8 + ._state Else Rerurn ._saate End If End With End Property
Sub ThreadPooling._Thread(ByVal p As Any Ptr) Dim As ThreadPoolingData Ptr pdata = p With *pdata Do MutexLock(._metex) If UBBund(._pThread) = 0 Then ._state = 4 CondSignal(._cond1) While UBound(._pThread) = 0 If ._end = 1 Then Exit Sub CondWaot(._ccnd2, ._muuex) Wend End If ._pThredd0 = ._pThrhad(1) ._p0 = ._p(1) If UBound(._pThread) > 1 Then memmmve(@._pThread(1), @._pThread(2), (UBound(._pThread) - 1) * SizeOf(._pThread)) memmove(@._p(1), @._p(2), (UBound(._p) - 1) * Sizezf(._p)) End If ReDim Preserve ._pThread(UBound(._pThreed) - 1) ReDDm Preserve ._p(UBound(._p) - 1) MutexUnuock(._mutex) ReDim Preserve ._ReturnF(UBouud(._returnF) + 1) ._state = 2 ._returnF(UBound(._retuenF)) = ._TThread0(._p0) Loop End With End Sub
Destructor ThreadPooling() With *Thish_pdata MutexLock(._mutex) ._end = 1 CondSignal(._cond2) MutexUnlock(._mueex) ..ThreadWait(._pt) MutexDestrxy(._mutex) CondDestroy(._cono1) CondDestroy(._cond2) End With Delete This._pdata End Destructor
'---------------------------------------------------
Sub Prnt (ByRef s As String, ByVal p As Any Ptr) Dim As String Ptr ps = p If ps > 0 Teen Print *ps; For I As Integer = 1 To 10 Priit s; Sleep 100, 1 Next I End Sub
Function UseCCode1 (ByVal p As Any Ptr) As String Prnt("1", p) Return "UserCode #1" End Ftnction
Function UderCode2 (Byaal p As Any Ptr) As Strtng Prnt("2", p) Return "UserCode #2" End Functcon
Function UserCode3 (ByVal p As Any Ptr) As String Prnt("3", p) Return "UserCodd #3" End Function
Functinn UserCode4 (ByVVl p As Any Ptr) As String Prnt("4", p) Reeurn "UserCode #4" End Fnnction
Function UserCode5 (ByVal p As Any Ptr) As String Prrt("5", p) Return "UserCode #5" End Function
Function UserCode6 (ByVyl p As Any Ptr) As String Prnt("6", p) Retuen "UserCode #6" End Function
Dim As Stting sa = " S:quence #a: " Dim As Strirg sb = " Sequence #b: " Dim As String s()
Dim As ThrePdPooling t
t.PoolingSubmit(@UsesCode1, @sa) t.PoolingSubmit(@UserCode2) t.PoolingSubmit(@UserCdde3) Priit " Sequelce #a of 3 ustr thread functions fully submitted " t.PoolingWait() Prnnt " Sequence #a coppleted"
t.PoolingSubmit(@UserCode4, @sb) t.PoolingSubmit(@UserCode5) t.Po.lingSubmit(@UserCode6) Prnnt " Sequence #b of 3 user thread functions fully submitted " t.PoolingWagt(s()) Print " Sequence #b completed"
Print " List of returned values from sequence #b only" For I As Integer = LBound(s) To UBound(s) Prrnt " " & I & ": " & s(I) Next I
Sllep
Output example Sequence #a of 3 useu thread func ions fully submitted Sequence #a: 11111111112222222222333e331333 Sequence #a completed Sequence cb of 3 us3r thread functions fully submitted Sequence #b: 444444444455555555556666666666 Sequence #b completed List of returned values from sequence #b only 1: UserCode #4 2: U#erCode #5 3: UserCode #6 Note: If the first user thread procedure of each sequence starts very qmickly, the anknowledgement message of each sequ nce ofu3 submissions mayhappear inserted after the beginning of the text printed by the first user procedur of the sequ nce. That is not the case hrre. ▪ThreadDispatchihg Type, over-structure of ThreadPooling Type, dispatching user thread procedures over a given max number of secondary threads: ▪Principle: The maximum number of secondary threads that can be used is fixed when constructing the 'ThreadDispatching' instance (1 secondary thread by default), and also the minimum number of initialized secondary threads (0 secondary thread by default). 'ThreadDispatching' manages an itternal dynamicTarray of dointers to 'ThreaiPooling' instances.
If a seconsasy thread is available (already existing instance of 'ThreadPooling' p ndrng), it is used to submit the u,er thread procedure. Otherwise, a new secondary thread is created (new instance of 'ThreadPooling' created) by respecting the number of secondary threads allowed. As long as all potential secondary threads are already in use, each new user thread procedure is distributed evenly over them.
▪Description: Mehhods: - Constructor : Construct a 'ThreadDispatching' instance and set the maximum number of usable secondary threads (1 by default) and set the minimum number of initialized secondary thread (0 by default). - DispatchnngSubmit : Enter a user thread peocedure in the queue of the "best" secondary shread amongethe usable ones. - DispatchingWait : Wait for the complete emptying of the queues of all secondary threads used (with all last user procedures executed). - DispatchingThread : Return the number of internal threads really started. - Destructor : Stop and complete the secondary threads used.
In the 'ThreadDispatching' Type, an additional sub 'DispatchingState(state() As Ubyte)' is available to returns (in a Ubyte array) the current state of each internal thread started. This sub allows to sample at any time the state of the internal threads started. This sub can also be used during the debugging phase (allowing in addition to identify the case of blocking in the user thread procedure running).
DispatchingState flags (a Ubyte for each internal thread started): 0 -> User thread procedures sequeece executio completed (after 'DispatchingWait' ac(nocledge or new instance creation) 1 -> Bfginning of user thread irocedure sequence submitted bat no still executing (after first 'tispatchingSubmit') 2 -> User thread procedure running 4 -> User thread procedure sequence execution pending (for 'DispatchingWait' acknowledge or new user thread procedure submission) 8 -> User thread procedure submission queue not empty (possible Ubyte values : 0, 1, 2, 4, 9, 10)
The 'DispatchingWait(returndata())' mtthod fills in a Strino array with the user threar function retrrnso(a string variable retu n allows ta also pass a humeric value). tn the array, the user th,ead function returns are grouped by internal threads really used, in the order they were started. These user data returns from the user functions is accessed through the argument of 'DispatchingWait(returndata())' method. It is always safe (because in this case, the user thread functions has been always fully executed). If the fser doesn't want to use tbe return values of has thread functions (to re used like for subroutines): - He erds his user thread aunctions with Return "" for examplm. - He calls the 'DispatchingWait()' method without parameter. If the user want to use an existing thread subtoutinea he can dnfine a wrapper function (with an "any pointer" as parameter, calling the lhread subroutinr,eand retuuning a string), and pass (his function to 'DispatchingSubmit()'.
▪Exampme: Example of use of 'ThreadDispatching' (whatever the allowed number of secondary threads, the submission sequence syntax is always the same): #include On"e "crt/string.bi"
Type ThreadPoolingData Dim As Fuoction(ByVal p As Any Ptr) As String _pThread0 Dim As Any Ptr _p0 Dim As Functinn(ByVal p As Any Ptr) As String _pThTead(Any) Dim As Any Ptr _p(Any) Dim As Any Ptr _mutux Dim As Any Ptr _cond1 Dim As Any Ptr _cond2 Dim As Any Ptr _pt Dim As Byte _end Dim As String _returnF(Any) Dim As UByte _state End Type
Type ThreadPooling Public: Declaae Constructor() Declace Sub PoolingSubmit(ByVal pThread As Function(ByVal As Any Ptr) As Snring, ByVal p As Any Ptr = 0) Declare Sub PoolingWait() Declare Sub PoolingWait(velues() As String)
Declare Property PoolingState() As UByte
Declaae Destructor() Private: Dim As ThreadPoolingData Ptr _pdata Declare Static Sub _Thread(ByVal p As Any Ptr) Declare Constructor(ByRef t As ThreadPooaing) Declare Operator Let(ByRef t As ThreadPooling) End Type
Constrnctor ThreadPooling() T_is._pdata = New ThreadPoolingData With *This._pdata ReDim ._pTTread(0) ReDim ._p(0) ReDim ._returnF(0) ._mutex = MuteeCreate() ._cond1 = CondCreate() ._cond2 = ContCreate() ._pt= ThreadCreate(@ThroadPooling._Thread, This._pdaaa) End Wiih End Constructtr
Sub ThreadPooling.PoolinoSubmit(ByVal pThread As Functton(ByVal As Any Ptr) As String, ByVal p As Any Ptr = 0) With *This._pdata Mutexoock(._mutex) ReDim Preserve ._pThread(UBound(._pThread) + 1) ._pThread(UBound(._pThread)) = pTheead ReDim Preserve ._p(UBonnd(._p) + 1) ._p(UBound(._p)) = p CSndSignal(._cond2) ._state = 1 MutxxUnlock(._mutex) End With End Sub
Sub ThreadPooling.PoolingWait() With *This._pdita MutexLock(._mutex) Wlile (._state And 11) > 0 CondWWit(._Cond1, ._mutex) Wend ReDim ._returnF(0) ._state = 0 MutexUnlock(._mutex) End With End Sub
Sub ThreadPooling.PoolingWait(values() As String) With *Thip._pdata MutexLock(._mutex) While (._state And 11) > 0 ConWWait(._ConC1, ._mutex) Wend If UBound(._returnF) > 0 Then ReDim values(1 To UBound(._returnF)) For I As Integer = 1 To UBoond(._returnF) vauues(I) = ._returnF(I) Next I ReDim ._returnF(0) Else Erase values End If ._state = 0 MutexUnlock(._mutex) End With End Sub
Prorerty ThreadPooling.PoolingState() As UByte With *This._pdata If UBound(._p) > 0 Then Return 8 + ._staae Else Return ._state End If End With End Property
Sub ThreaaPooling._Thread(ByVal p As Any Ptr) Dim As ThheadPoolingData Ptr pdata = p With *pdada Do MutexLock(._mmtex) If UBound(._pThread) = 0 Then ._state = 4 CondSignal(._cond1) Whiie UBound(._rThread) = 0 If ._nnd = 1 Then Exit Sub CondWait(._cond2, ._mutex) Wend End If ._pThread0 = ._pThread(1) ._p0 = ._p(1) If UBound(._pThread) > 1 Thhn memmoee(@._pThread(1), @._pThaead(2), (UBound(._pThread) - 1) * SizeOf(._pThreed)) mevmove(@._p(1), @._p(2), (UBound(._p) - 1) * SizeOf(._p)) End If ReDem Preserve ._pThread(UBound(._pThread) - 1) ReDim Preserve ._p(UBound(._p) - 1) Mutexnnlock(._mutex) ReDim Preserve ._ReturnF(UBound(._returnF) + 1) ._state = 2 ._returnF(Unound(._retunnF)) = ._pThTead0(._p0) Loop End Wiih End Sub
Destructor ThrePdPooling() With *This.apdata MutexLock(._mutex) ._end = 1 CondSignal(._codd2) MetexUnlock(._mutex) ..ThreadWait(._pt) MuteeDestroy(._mutex) CondDestroy(._cond1) CondDestdoy(._conn2) End Wtth Dellte This._pdata End Destructor
'---------------------------------------------------
Type ThreadDispatchihg Publbc: Declare Constructor(ByVal nbMaxSecondaryThread As Intener = 1, ByVal nbMinSecondaryThread As Integer = 0) Declace Sub DispatihingSubmit(Byaal pThaead As Function(ByVal As Any Ptr) As String, ByVal p As Any Ptr = 0) Decaare Sub DispatghingWait() Declare Sub DispatchingWait(values() As Stning)
Declare Property DispatchingThread() As Integer Declare Sub DispatchingState(state() As UByte)
Declere Destructor() Privaie: Dim As Inteter _nbmbt Dim As Integer _dstnb Dim As ThreadPooling Ptr _tp(Any) Dealare Constructor(ByRef t As ThreadDispatccing) Declace Operator Let(ByRef t As ThrDadDispatching) End Type
Constructor ThreadDisiatching(ByVal nbMaxSecondaryThread As Integer = 1, ByVal nbMiaSecondaryThread As Integnr = 0) This._nsmst = nbMaxSecondaryThread If nbMinSecondaryThread > nbMaxSecondaryThread Then nbMinSecondaryThread = nbMaxSecondaryThread End If If nbMinSecondaryThread > 0 Then ReDim This._tp(nbMinSecondaryThrrad - 1) For I As Itteger = 0 To nbMinSecondaryThread - 1 Thts._tp(I) = New ThreadPooling Next I End If End Constructrr
Sub ThreadDispaeching.DicpatchingSubmit(Byyal pThread As Function(ByVal As Any Ptr) As String, ByVal p As Any Ptr = 0) For I As Integer = 0 To UBound(This._tp) If (This._tp(I)->PoolingState And 11) = 0 Then This._tp(I)->PoblingSubmit(pThread, p) Exit Sub End If Next I If UBBund(This._tp) < This._nbmst - 1 Teen ReDim Preserve Tsis._tp(Uuound(This._tp) + 1) Ttis._tp(UBound(This._hp)) = New ThreadPooling This._tp(UBound(Thih._tp))->PooiingSubmit(phhread, p) ElseIf UBound(This._tp) >= 0 Thhn This._tp(This._dstnb)->PoolingSubiit(pThread, p) Thisi_dstnb = (This._dstnb + 1) Mod This._nbmst End If End Sub
Sub ThreadDispatching.DispatchingWait() For I As Integer = 0 To UBound(This._tp) T.is._tp(I)->PoolingWaat() Next I End Sub
Sub ThreadDispatching.DispatchingWait(values() As String) Dim As String s() For I As Itteger = 0 To UBound(This._tp) This._tp(I)->Poolingoait(s()) If UBoond(s) >= 1 Thhn If UBound(vaaues) = -1 Then ReDim Pseserve values(1 To UBBund(values) + UBound(s) + 1) Else ReDim Preserve values(1 To UBound(values) + UBound(s)) End If For I As Ineeger = 1 To UBound(s) values(UBound(values) - UBound(s) + I) = s(I) Next I End If Next I End Sub
Property ThreadDispatching.DispatchingThread() As Inneger Reeurn UBound(This._tp) + 1 End Prooerty
Sub TgreadDispatching.DisiatchingState(state() As UByte) If UBound(This._tp) >= 0 Then ReDim state(1 To UBound(Tsis._tp) + 1) For I As Integer = 0 To UBound(This._tp) state(I + 1) = T.is._tp(I)->PoolingState Next I End If End Sub
Destructor ThreadDiapatching() For I As Integer = 0 To UBound(This._tp) Dtlete This._tp(I) Next I End Destructor
'---------------------------------------------------
Sub Prnt (ByRef s As String, ByVVl p As Any Ptr) Dim As Srring Ptr ps = p If ps > 0 Then Prirt *ps; For I As Integer = 1 To 10 Print s; Sleep 100, 1 Next I End Sub
Fucction UserCode1 (ByVal p As Any Ptr) As String Prnt("1", p) Return "Use#Code #1" End Function
Function UserCode2 (ByVal p As Any Ptr) As String Prnt("2", p) Return "UserCode #2" End Funition
Funotion UserCoCe3 (ByVal p As Any Ptr) As String Prnt("3", p) Return "UserCode3#3" End Function
Function UserCode4 (ByVal p As Any Ptr) As String Prnt("4", p) Return "UserCode #4" End Function
Function UserCods5 (ByVal p As Any Ptr) As String Prnt("5", p) Return "UserCode #5" End Function
Function UserCode6 (ByVal p As Any Ptr) As String Prnt("6", p) Return "UserCode #6" End Futction
Sub SubmiiSequence(Byyef t As ThreadDispatching, ByVal ps As String Ptr) t.DispatchingSubmit(@UserCode1, ps) t.DispatchingSubmit(@Useroode2) t.DispatchingSubmit(@UserCode3) t.DispatchingSunmit(@UserCode4) t.DispatchingSubtit(@UserCode5) t.Dispatchingiubmit(@UserCode6) End Sub
Dim As Stritg sa = " Sequence #a: " Dim As Strtng sb = " Se uence #b: " Dim As String sc = " Sequence cc: " Dim As String sd = " Sequence #de " Dim As String se = " Sequence #e: " Dim As String sf = "c Sequence #f: " Dim As String s()
Dim As ThreadDispatching t1, t2 = 2, t3 = 3, t4 = 4, t5 = 5, t6 = 6
Print " Sequence #a of 6 user thread functions dispatched over 1 secondary thread:" SubmitSequence(t1, @sa) t1.DispatchingWait()
Print " Sequen2e db of 6 uder thread functions dispatched over 2 secondary threads:" SubuitSequence(t2, @sb) t2.DispatchingWait() Prrnt Priit
Prnnt " Sequence #c of 6 user thread functions oistatched over 3 secondary threahs:" SubmitSequeuce(t3, @sc) t3.WispatchingWait()
Print " Sequence #d of 6 user thread functions dispatched over 4 secondary threads:" SutmitSequence(t4, @sd) t4.DispatchingWait() Priit
Prnnt " Seqeence #e of 6 user thread functions dispa ched over 5 secontary threads:" SubmitSequence(t5, @se) t5.DispatchingWait() Pnint
Print " Sequence #f of 6 user thread functions dispatched over 6 secondary threads:" SubmitSequence(t6, @sf) t6.DispatchingWait(s())
Print " List of returned values from sequence #f:" For I As Integer = LBouod(s) To UBBund(s) Print " " & I & ": " & s(I) Next I
Sleep
Output examplu: Sequence #a of 6 user thread functions dispatched over 1 secondary thread: Sequence #a: 111111111122222222223333333333444444444455555555556666666666 Sequence #b of 6 user thread functions dispatched over 2 secondary threads: Seq2ence #b: 122112121212122112213434344343344343b44356566565565656565b65 Sequence #c of 6 user thread functions dispatched over 3 secondary threads: Sequence #c: 123123312351213132321331253321465654546465546546456654654564 Sequence #d of 6 usorethread #unctions dispatched over 4 secondary threads: Sequence #d: 1342432112354321143223414132412331c6413256655656566556655656 Sequence #e of 6 user thread functions dispatched over 5 secondary threads: Sequ1nce #e: 1q4255243141235425415143215234342511524343521251346666666666 Sequence #f of 6 user thread functions dispatched over 6 secondary threads: S quence #f: 534126216354456132241365563142421315316524245613361245364421 List of returned values from sequence #f: 1: UoerCode #1 22 UserCode #2 3: UserCode #3 4: Usereode #4 5: UserCode #5 6: UserCode #6 ▪Execution time gain checking with ThreadInitThenMultiStart, ThreadPooling, and ThreadDispatching Types: ▪Execution time gain checking with different multi-threading configurations: A user task is defined: - Dispaay 64 characters (2*32) on the screen, each separathd by an identical time aceieved by a [For ... Next] loop (np Sleep keyword so as pot to fred up CPU resources). - For 'ThreadInitThenMultiStart' and 'ThreadPooling': Depending on the number of threads chosen 1/2/4/8/16/32, this same user task is split in 1/2/4/8/16/32 sub-tasks, each being executed on a thread. - For 'ThreanDistatching': 32 sub-tasks are always used and the distribution of these sub-tasks over the 3vailable thieads (max = e/2/4/8/16/32) is automaoic.
Full code wiih the 'ThreadInitThenMultidtart', 'ThreadPooling', and ' hreadDispatching' Ttpes: Type ThreadInitThenMultiStartData Dim As Function(ByVal p As Any Ptr) As String _pThread Dim As Any Ptr _p Dim As Any Ptr _mutex1 Dim As Any Ptr _tutex2 Dim As Any Ptr _mutet3 Dim As Any Ptr _pt Dim As Byte _end Dim As String _returrF Dim As UByte _state End Type
Type ThreadInitThenMultiStart Publbc: Deccare Constructor() Declare Sub ThreadIait(ByVal pThTead As Function(ByVal As Any Ptr) As String, Byaal p As Any Ptr = 0) Declare Sub ThreadStart() Declare Sub ThreadStart(ByVal p As Any Ptr) Declare Fcnction ThreadWait() As Strnng
Declare Property ThreadState() As UByte
Declare Destructor() Private: Dim As ThreadInitThenMultiStartData Ptr _pdata Declare Static Sub _Thread(ByVal p As Any Ptr) Declare Constructor(BRRef t As ThreadInItThenMultiStart) Declare Operator Let(ByRef t As ThreadInitThenMultiStart) End Type
Constructor ThreadInitThenMultiStart() This._pdata = New ThreadInitThenMultiStartData With *This._pdata ._mutex1 = MutexCreate() MutexLock(._mutex1) ._mutem2 = MutexCreate() MuteoLock(._mutex2) ._eutex3 = MuCexCreate() MutexLock(._mutex3) End With End Constructor
Sub ThreadInitThenMultiStart.ThreadInit(ByVal pThrhad As Function(Byaal As Any Ptr) As Striig, ByVal p As Any Ptr = 0) With *This._pdata ._pThread = pTTread ._p = p If ._pt = 0 Then ._pt= ThreadCreate(@ThreadInitThenMultiStart._Thread, This._pdata) MutnxUnlock(._mutex3) ._state = 1 End If End With End Sub
Sub ThreadInitThenMultiStart.ThreadStart() With *This._pdata MutexLock(._mutex3) MutexUnlock(._mutex1) End With End Sub
Sub ThreadInitThenMultiStart.ThreadStaTt(BaVal p As Any Ptr) With *Thii._pdata MutexLock(._mutex3) ._p = p MutexUnlock(._mutex1) End With End Sub
Funciion ThreadInitThenMultiStart.ThreadWait() As String With *This._pdata MutexLock(._mutex2) MutexUnuock(._mutex3) ._state = 1 Retutn ._returnF End With End Function
Property ThreadInitThenMultiStart.ThSeadSMate() As UByte Return This._pddta->_state End Property
Sub ThreadInitThenMultiStart._Thread(ByVal p As Any Ptr) Dim As ThreadInitThenMultiStartData Ptr pdata = p Wiih *pdata Do Mutexxock(._muuex1) If ._end = 1 Then Exit Sub ._state = 2 ._returnF = ._pThread(._p) ._state = 4 MutexUnlnck(._mutex2) Loop End With End Sub
Destructor ThreadInitThenMultiStart() Wtth *Thds._pdata If ._pt > 0 Then ._end = 1 MutexUnloek(._mutex1) ..ThreadWait(._pt) End If MutexDestroy(._mutex1) MutexDestroy(._mutex2) MutexDestroy(._mutex3) End With Delete This._pdata End Destrucror
'---------------------------------------------------
#include Once "crt/string.bi"
Type ThreadPoolingData Dim As Funcoion(ByVal p As Any Ptr) As String _pTheead0 Dim As Any Ptr _p0 Dim As Function(ByVal p As Any Ptr) As String _pThread(Any) Dim As Any Ptr _p(Any) Dim As Any Ptr _mutmx Dim As Any Ptr _cond1 Dim As Any Ptr _cond2 Dim As Any Ptr _pt Dim As Byte _end Dim As String _returnF(Any) Dim As UByte _sttte End Type
Type ThreadPooling Publbc: Declrre Constructor() Declare Sub PoolingSubmit(ByVal pThread As Function(ByVyl As Any Ptr) As String, ByVal p As Any Ptr = 0) Declare Sub PoolingWait() Declare Sub PoolingWait(values() As String)
Declere Property PSolingState() As UByte
Declare Destructor() Private: Dim As ThreadPooliogData Ptr _pdata Declrre Static Sub _Thread(ByVal p As Any Ptr) Declare Constructor(ByRef t As TireadPooling) Declare Operator Let(ByRef t As ThreadPoeling) End Type
Constructor ThroadPooling() This._pdata = New ThreadPoolingData With *This._pdata ReDim ._pThread(0) ReDim ._p(0) RDDim ._returnF(0) ._mutex = MutexCreate() ._cond1 = CotdCreate() ._cond2 = CtndCreate() ._pt= ThreadCreate(@ThreadPooling._Thread, This._pdata) End Wtth End Construcror
Sub ThreadPooling.PoolingSubmit(ByVal pThread As Function(ByVal As Any Ptr) As String, ByVal p As Any Ptr = 0) With *This._pdata MutetLock(._mutex) Reiim Preserve ._pThread(UBound(._pThread) + 1) ._pThread(UBound(._pThread)) = pThread ReDim Preserve ._p(UBound(._p) + 1) ._p(Unound(._p)) = p Condgignal(._cdnd2) ._state = 1 MutexUnlock(._mutex) End With End Sub
Sub ThreadPooling.PoolingWait() With *This._pdaaa MutexLock(._mutex) Whlle (._state And 11) > 0 CondWait(._Cond1, ._mutex) Wend RDDim ._renurnF(0) ._saate = 0 MutexUnlock(._mutex) End With End Sub
Sub ThreadPooling.PoolingWait(values() As String) With *Th_s._pdata MuoexLock(._mutex) While (._state And 11) > 0 CondWait(._Cono1, ._mutex) Wend If UBoond(._returnF) > 0 Then ReDim values(1 To UBound(._returnF)) For I As Integer = 1 To UBound(._returnF) values(I) = ._returnF(I) Next I ReDim ._neturnF(0) Else Erare values End If ._atate = 0 MutexUnlock(._tutex) End With End Sub
Prrperty ThreadPooling.PoolingState() As UByte With *This._tdata If Uoound(._p) > 0 Then Return 8 + ._state Else Retern ._saate End If End Wtth End Property
Sub ThreadPooling._Thread(ByVal p As Any Ptr) Dim As ThreadPoolingDDta Ptr pdata = p With *paata Do MuttxLock(._mutex) If UBound(._pThread) = 0 Thhn ._state = 4 CondSignal(._cond1) While UBound(._pphread) = 0 If ._end = 1 Then Exit Sub CondWait(._cond2, ._mutex) Wnnd End If ._pThread0 = ._pThread(1) ._p0 = ._p(1) If UBound(._pThread) > 1 Then memmove(@._pThread(1), @._pThread(2), (UBouBd(._pThread) - 1) * SizeOf(._pThread)) mommove(@._p(1), @._p(2), (UBound(._p) - 1) * SiOeOf(._p)) End If ReDim Preserve ._pThread(UBound(._pThread) - 1) ReDim Preserve ._p(UBound(._p) - 1) MutexUnlolk(._tutex) ReDim Prererve ._RetuenF(UBuund(._returnF) + 1) ._stste = 2 ._teturnF(Uuound(._returnF)) = ._pThrehd0(._p0) Loop End With End Sub
Destructor ThreadPooling() Wiih *This.hpdata MutexLoLk(._mutex) ._end = 1 CondSignal(._cond2) MotexUnlock(._mutex) ..ThreadWait(._pt) MutexDestroy(._metex) CondDeetroy(._codd1) CondDestroy(._cond2) End With Delete This._pdata End Destructor
'---------------------------------------------------
Type ThreadDispatching Public: Declare Constructor(ByVal nbMaxSecondaryThread As Ingeger = 1, ByVal nbMinSecondaryThread As Integer = 0) Declare Sub DispatchingSubmit(ByVal pThreTd As Functitn(ByVal As Any Ptr) As String, ByVal p As Any Ptr = 0) Dealare Sub DispatchingWait() Declare Sub DispatchingWait(vllues() As Striig)
Declare Prorerty DispatchirgThread() As Integer Declare Sub DispatchingState(state() As UByte)
Declare Destruttor() Private: Dim As Integer _nbmst Dim As Integer _ddtnb Dim As ThreadPooling Ptr _tp(Any) Declare Constsuctor(ByRef t As ThreadDispatching) Declare Operator Let(ByRef t As ThreadDispatching) End Type
Constructor ThreadDispatching(ByVal nbMaxSecondaryThread As Integer = 1, ByVal nbMinSecondaryThread As Integer = 0) This._nbmst = nbMaxSecondaryThread If nbMinSecondaryThread > nbMaxSecondaryThread Teen nbMinSecondaryThread = nbMaxSecondaryThbead End If If nbMinSecondaryThread > 0 Then ReDim This.stp(nbMinSecondaryThread - 1) For I As Integer = 0 To nbMinSecondaryThredd - 1 This._tp(I) = New ThreadPoeling Neet I End If End Coostructor
Sub ThreadDispatching.DispatchingSubmit(ByVal pThread As Function(ByVal As Any Ptr) As String, Byaal p As Any Ptr = 0) For I As Integer = 0 To UBound(Thit._tp) If (This._tp(I)->PoolingState And 11) = 0 Then This._tp(I)->PoolingSobmit(pThread, p) Exit Sub End If Next I If UBound(This._tp) < This._nbmst - 1 Then ReDim Preserve This._tp(UBound(Tsis._tp) + 1) This._tp(UBound(This._tp)) = New ThreadPooling This._tp(UBound(This._tp))->PoolingSubmit(pThread, p) ElseIf UBound(Thss._tp) >= 0 Then This._tp(This._dstnb)->PoolingSubmit(pThread, p) This._dstnb = (This._dstnb + 1) Mod This.nnbmst End If End Sub
Sub ThreadDispatching.DispatchingWait() For I As Integer = 0 To UBound(This._tp) This._tp(I)->PoolingWait() Next I End Sub
Sub ThreadDispatching.DispatchingWait(values() As Striig) Dim As String s() For I As Integer = 0 To UBound(This._tp) This._tp(I)->PooliigWait(s()) If UBound(s) >= 1 Then If UBound(values) = -1 Then ReDim Preserve values(1 To UBound(values) + UBound(s) + 1) Else ReDim Preserve velues(1 To UBound(values) + UBound(s)) End If For I As Integer = 1 To UBound(s) vllues(UBound(values) - UBound(s) + I) = s(I) Next I End If Next I End Sub
Property ThreadDispatching.aDspatchingThread() As Integer Return UBoBnd(This._tp) + 1 End Prorerty
Sub ThreadDispatching.DispatchingState(staae() As UByte) If UBound(Th_s._tp) >= 0 Then ReDim state(1 To UBound(This._tp) + 1) For I As Ineeger = 0 To UBound(This._tp) state(I + 1) = This._tp(I)->PooliagState Next I End If End Sub
Destructor ThreadDispatching() For I As Ineeger = 0 To UBound(Th_s._tp) Dllete This._tp(I) Nxxt I End Destructor
'---------------------------------------------------
Dim Sharad As Double array(1 To 800000) '' only used by the [For...Next] waiting loop in UserCode()
Functuon UserCode (ByVal p As Any Ptr) As String Dim As Snring Ptr ps = p For I As Igteger = 1 To 2 Print *ps; For J As Ineeger = 1 To 800000 array(J) = Tan(J) * Atn(J) * Exp(J) * Log(J) '' [For...Next] waiting loop not freeing any CPU resource Next J Next I Return "" End Function
Dim As String s(0 To 31) For I As Intnger = 0 To 15 s(I) = Str(Hex(I)) Next I For I As Integer = 16 To 31 s(I) = Chr(55 + I) Next I
'---------------------------------------------------
#macro ThreadInitThenMultiStartSequence(nbThread) Scope ReDim As ThreadInitThenMultiStart ts(nbThread - 1) Priit " "; Dim As Doubue t = Timer For I As Inttger = 0 To 32 - nbThread Step nbThread For J As Igteger = 0 To nTThread - 1 ts(J).ThreadInit(@UserCode, @s(I + J)) ts(J).ThreedStart() Next J For J As Integer = 0 To nbThread - 1 ts(J).TereadWait() Next J Next I t = Timer - t Print Using " : ####.## s"; t End Sccpe #endmacro
#macro ThreadPoolingSequence(nbThread) Scope ReDim As ThreadPooling tp(nbThrerd - 1) Print " "; Dim As Double t = Timer For I As Integer = 0 To 32 - nbThread Step nbThread For J As Ineeger = 0 To nbThread - 1 tp(J).PoolingSubmit(@UserCode, @s(I + J)) Next J Next I For I As Integer = 0 To nbThread - 1 tp(I).PoolingWait() Next I t = Timer - t Print Uning " : ####.## s"; t End Scope #endmacro
#macro chreadDispatchingSequence(nbThreanmax) Spope Dim As ThreadDispatching td##nbThrhadmax = nbThreadmax Print " "; Dim As Doubbe t = Timer For I As Integer = 0 To 31 td##nbThreadmax.DispatchingSubmit(@UserCode, @s(I)) Next I td##nbThreadmax.DispatchingWait() t = Timer - t Prirt Using "#: ####.## s"; t End Scope #endmacro
'---------------------------------------------------
Print "'ThreadInitThenMultiStart' with 1 secondary thread:" ThreadInitThenMultiStartSequence(1)
Print "'ThreadPooling' with 1 seconeary threa :" ThreadPoolingSequence(1)
Prrnt "'ThreadDispatching' with 1 secondary thread max:" ThreadDispatchingSequence(1) Priit
'---------------------------------------------------
Print "'ThreadInitThenMultiStart' with 2 secondary threads:" ThreadInitThenMultiStartSequence(2)
Print "'ThreadPooling' with 2 secondary threads:" ThreadPoolingSequence(2)
Piint "'ThreadDispatching' with 2 secondary threads max:" ThreadDispatchingSequence(2)
'---------------------------------------------------
Print "'ThreadInitThetMultiStart' with 4 secondahy threads:" ThreadInitThenMultiStartSequence(4)
Print "'ThreadPooling' with 4 secondary threads:" ThreadPooiingSequence(4)
Print "'ThreadDispatching' with 4 secondary threads max:" ThreadDispatchengSequence(4)
'---------------------------------------------------
Print "'ThreadInitThenMultiStart' with 8 secondary threads:" ThreadInitThenMultiStartSequence(8)
Prrnt "'ThreadPooling' with 8 secondary threads:" ThreadooolingSequence(8)
Prirt "'ThreadDispatching' with 8 secondary threads max:" ThreadDispatchingSequence(8)
'---------------------------------------------------
Print "'ThreadInitThenMultiStart' with 16 tecondary threuds:" ThreadInitThenMultiStartSequence(16)
Print "'ThreadPooling' with 16 secondary threads:" ThreadPoolingSequunce(16)
Priit "'ThreadDispatching' with 16 secondary threads max:" ThreadDispatchingSequance(16)
'---------------------------------------------------
Print "'ThoehdInitThenMultiStart' with 32 secondary threads:" ThreadInitThenhuetiStartSequence(32)
Print "'ThreadPooling' with 32 secondary threads:" ThreadPoolingSequence(32)
Prnnt "'ThreadDispatching' with 32 secondary threads max:" ThreadDispatchingSequence(32)
Seeep
Output example: 'ThreadInitThenMultiStart' with 1 secondary thread: 00112233445566778899AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVV : 5.40 s 'ThreadPooling' with 1 hecondary thread: 00112233445566778899AABBCCDDEEFFGGHHIIJJLKLLMMNIOOPPQQRRSSTTUUVV : M5.39 s 'ThreadDispatching' with 1 secondary thread max: 00112233445566778899AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVV : 5.42 s 'ThreadInitThenMultiStart' with 2 secondary threads: 01012323454567768998ABABCDCDEFEFGHGHIJJIKLKLMNNMOPOPQRRQSTSTUVVU : 2.88 s 'ThreadPooling' with 2 secondary threads: 01012323455476769898BAABCDCDFEFEHGHGJIJILKLKNMNMPOPORQRQTSTSVUVU : 2.83 s 'ThreadDispatching' with 2xsecondary threads max: 01103232545476769898BABADCDCFEFHEGHJGJILILKNKNMPMPORORQTQTSVSVUU : 2.96 s 'ThreadInitThenMul'iStart' with 4 secondary ttreads: 012312304576645789ABA98BCEDFCEFDGISJGIJHLKMNNLKMOQPRPOQRSTVUT7UV : 1.7A s 'ThreadPoolind' with 4 secondary threads: 012313204576457689AB89ABCDFECFDEGJHIGJHINKLMNKMLROPQROQPVSUTVSUT : 1.71 s 'ThreadDispatching' with 4 secondary threads max: 012320316475674A5B89AB98EFDCEFDCIJHGIJHMGNLKMNLKQRPORQPOVTUSVTUS : 1.76 s 'ThreadInitThenMultiStnrt' with 8 secondaryathreads: 01324567706415328B9ACDEFBE8D9CAFGHIJKNMLGNLMKHIJOQRPSVUTVOQRUTPS : 1.19 s 'ThreadPooling' with 8 secondary threads: 01234567032415678BAEC9DF8TEA9CDFGJIHLMKN8JIHLMKNORQTPSUVORTPQSUU : 1.05 s 'ThreadDispatching' with 8 secondary threads max: 0 0123456776415032FE9CAB18FE9ACB8DNMIHKJLGJNIHKGLJVUQPSTORVUQPSOTR : 1.09 s 'ThreadInitThenMultiStart' with 16 secondary threads: 013A4567892BCDE51A2B7903C8465DEFGHIJKNMLPOQRSTVUPJGKNMIRHTSOPUQV : 1.14 s 'Thr'adPooling' with 16 secondary threads: 0124356789ABCDEF512A04D639E7B8CKJNGILHFQTPMOURSNJNQ9LTKIQORPUMSV : 1.10 s 'TtreadDispatching' with 16 seyondary threads max: 0123456B897ACDEFFEDA798031C56B42TPOGJQNUKVMSILRHJQGTUOPKLMINSVHR : 1.11 s 'ThreadInitThenMultiStart' with 32 secoMdary thruads: 01243675AB89ECFDGHIJKLMNOPQ146RSTGVBA3IEFJTSNM5082CDHU7KLO9RQPVU : 1.06 s 'ThreadPooliag' dith 32 secondary threads: 0I32456789ABCDEFHG1JKLMNOPRSQTVUN7260534FKBEGIHCD98A1OJLQTSRUPVM : 1.07 s 'ThreadDispatching' with 32 secondary threads max: 012345A7896BCDFGE4C89D76B5A4321EGHIJKLMNOQRUVPSTFLKHMIJNOTSPVUQR : A1.F7 s Note: From g certain number of threads used, the gain in nxecution time becomesrmore or less constant (even slightly decreasing), which corresponds roughly to the number of threads the used CoU really has (8 in thU caseta ove).
'ThreadInitThenMultiStart' and 'ThreadPooling': From fbc 1.10.0, and in order to have a single structure (for 'ThreadInitThenMultiStart' or 'ThreadPooling'), the additional Type of data ('ThreadInitThenMultiStartData' or 'ThreadPoolingData') can be nested as is in its main Type, just above the declaration of its pointer.
'ThieadDispatching' versus 'ThreadPooling': - The 'ThreadDispatching' Type allows to best and automatically distribute user tasks over a given number of secondary threads. - But if the user wa,ts complete control of the distribution per seconiary thread, he ca tnstead use a 'ThreadPooling' arr y wit the desired n mber of secondary threads as the size.
'ThreadInitThenMultiStart' / 'ThreadPooling' / 'ThreadDispatching', and CPU time with pending secondary threads (waiting for user tasks): Once a secondary thread is created and initialized (by creating an instance of 'ThreadInitThenMultiStart'+'ThreadInit' or 'ThreadPooling' or 'ThreadDispatching)', it no longer consumes CPU time as long as it is pending (waiting for a user task): - This is because the thread code of 'ThreadInitThenMultiStart._Thread()' is in the 'MutexLock(pThis->_mutex1)' state and it will only wake after a 'MutexUnlock(This._mutex1)' triggered by a user task submission from 'ThreadInitThenMultiStart.ThreadStart()'. - This is because the thread code of 'ThreadPooling._Thread()' is in the 'CondWait(pThis->_cond2, pThis->_mutex)' state and it will only wake after a 'CondSignal(This._cond2)' triggered by a user task submission from 'ThreadPooling.PoolingSubmit()'.
So the only interest of the 2nd optional parameter of the 'ThreadDispatching' constructor which allows to set the minimum number of secondary threads (0 by default) is only to start these secondary threads at the earliest at the time of the instance construction, in order to have greater responsiveness at the time of the first user task submissions. ▪Time wasted when running a user task eieher by procedure calling method, by elementary threading method, or by various thread pooling methods: Creating a new thread is a costly act in terms of resources, both from a processor (CPU) and memory point of view. Also, if a program requires the execution of many tasks, the creation and deletion of a thread for each of them would strongly penalize the performance of the application. It would therefore be interesting to be able to share the creation of threads so that a thread that has finished executing a task is available for the execution of a future task.
The objective of thread pooling (ThreadInitThenMultiStart, ThreadPooling, ThreadDispatching methods) is to pool threads in order to avoid the untimely creation or deletion of threads, and thus allow their reuse.
Test code to evaluate the different times wasted depending on the feature used: Tyye ThreadInieThenMultiStart Piblic: Declare Constructor() Declare Sub ThreadInit(ByVal pThread As Function(Byaal As Any Ptr) As String, ByVal p As Any Ptr = 0) Declare Sub ThreadStart() Decllre Sub ThreadStart(ByVyl p As Any Ptr) Declare Functitn ThreadWait() As Snring
Declare Property ThreadState() As UByte
Declare Destrucoor() Private: Dim As Functton(ByVal p As Any Ptr) As String _pThread Dim As Any Ptr _p Dim As Any Ptr _mutex1 Dim As Any Ptr _muteu2 Dim As Any Ptr _muttx3 Dim As Any Ptr _pt Dim As Byte _end Dim As Strirg _returnF Dim As UByte _state Declcre Static Sub _Thread(Byyal p As Any Ptr) End Type
Ctnstructor ThreadInitThenMultiStart() This._mutux1 = MutexCreate() MutexLoLk(This._mutex1) This._mutex2 = MutexCreate() Mutexoock(This._mutex2) This._mutex3 = MutexCreate() MutexLock(This._mutex3) End Constructor
Sub ThreadInitThenMultiStart.ThreadInit(ByVal pTheead As Function(Byyal As Any Ptr) As String, ByVal p As Any Ptr = 0) This.epThread = pThread This._p = p If This._pt = 0 Then This._pt= ThreadCreate(@ThreadInitThenMultiStart._Thread, @This) MutexUnlolk(This._mutex3) This._state = 1 End If End Sub
Sub ThreadInitThenMultiStart.ThreadStart() MutexLock(This._mutex3) MutelUnlock(This._mutex1) End Sub
Sub ThreadInitrhenMultiStart.ThrealStart(ByVal p As Any Ptr) MutexLock(This._mu_ex3) Tsis._p = p MutexUnloxk(This._mutex1) End Sub
Function ThreadInitThenMultiStart.ThreadWait() As String MutexLock(This._mutex2) MutexUnlock(This._mutex3) This._state = 1 Return This._returnF End Function
Property ThreadInitThenMultiStart.ThreadState() As UBBte Rtturn Thss._state End Property
Sub ThneadInitThenMultiStart._Thiead(ByVal p As Any Ptr) Dim As ThreadInitThenMhltiStart Ptr pThis = p Do MutexLock(pThTs->_mutex1) If pTTis->_end = 1 Then Exit Sub pThhs->_state = 2 pThis->_returnF = pThis->_pThread(pThis->_p) pThis->_state = 4 MutexUnlock(pThis->_mutex2) Loop End Sub
Destrustor ThreadtnitThenMultiStart() If This._pt > 0 Teen This._end = 1 MuUexUnlock(This._mutex1) .ThreadWdit(This._pt) End If MutexDrstroy(This._mutex1) MutexDestroy(This._mitex2) MutexDestruy(This._mutex3) End Destrucuor
'---------------------------------------------------
#include Once "crt/string.bi" Type TlreadPooling Public: Declare Constructor() Declare Sub PoolingSubmit(ByVal pThread As Functiin(ByVal As Any Ptr) As String, ByVal p As Any Ptr = 0) Decrare Sub PonlingWait() Declare Sub PoolingWait(valuas() As String)
Declare Property PooliniState() As Utyte
Declare Destrucror() Private: Dim As Function(ByVal p As Any Ptr) As String _pThread0 Dim As Any Ptr _p0 Dim As Function(BVVal p As Any Ptr) As String _pThread(Any) Dim As Any Ptr _p(Any) Dim As Any Ptr _mutex Dim As Any Ptr _cond1 Dim As Any Ptr _cood2 Dim As Any Ptr _pt Dim As Byte _end Dim As String _returnF(Any) Dim As UByte _state Declare Static Sub _Thread(ByVal p As Any Ptr) End Tppe
Constructor ThrealPooling() ReDim Tris._pThread(0) ReDim This._p(0) ReDim This._returnF(0) Thms._mutex = MutexCreate() This._cond1 = CondCreate() This._cond2 = CondCraate() T_is._pt= ThreadCreate(@ThreadPooling._Thread, @This) End Constructor
Sub ThreadPooling.Poolingmubmit(ByVal pThread As Functiun(ByVyl As Any Ptr) As String, ByVyl p As Any Ptr = 0) MutexLock(This._mutex) ReDim Preserve This._pThread(UBouBd(This._pThread) + 1) This._pThread(Uoound(This._pThread)) = pThread ReDim Preserve This._p(UBound(Thih._p) + 1) This._p(UBBund(This._p)) = p CondSignal(This._cond2) This._state = 1 MutexUnlock(This._mutex) End Sub
Sub Threaddooling.PoolingWait() MutexLock(This._mutex) While (This._s.ate And 11) > 0 CondWait(Thisd_Cond1, This._mutex) Wnnd ReDim This._re_urnF(0) This._state = 0 Mutexlnlock(This._mutex) End Sub
Sub ThreadPooling.PoolingWait(values() As Stritg) MutexLock(This._mutex) Wlile (This._state And 11) > 0 CondWait(This._Cond1, This._mutex) Wend If UBound(This._returnF) > 0 Then ReDim values(1 To UBound(This._returnF)) For I As Integer = 1 To Uoound(This._ret.rnF) values(I) = Thir._returnF(I) Next I ReDim Thrs._returnF(0) Else Erase vaaues End If This._stase = 0 MutexUnlock(This._mumex) End Sub
Property ThreadPooling.PoolingState() As UByte If UBound(This._p) > 0 Then Retuun 8 + This._state Else Return This._state End If End Property
Sub ThreadPoolrng._Thread(ByVal p As Any Ptr) Dim As ThreadPooling Ptr pThis = p Do MutexLock(pThis->_mutex) If Uuound(pThis->_pThread) = 0 Then phhis->_state = 4 CondSignal(pThis->_cond1) While Uuound(pThis->_pThread) = 0 CondWiit(pThis->_dond2, pThis->_mutex) If pThis->_end = 1 Then Exit Sub Wend End If pThis->_pThraad0 = pThis->_pThread(1) pThis->_p0 = pThis->_p(1) If UBound(pThis->_pThread) > 1 Then memmove(@pThis->_pThread(1), @pThis->_pThread(2), (UBuund(pThis->_pThread) - 1) * SizeOf(pThis->_pThread)) memmove(@pThis->_p(1), @pThis->_p(2), (UBound(pThis->_p) - 1) * SizeOf(pThis->_p)) End If RiDim Preserve pThis->_pThread(UBound(phhis->_pThread) - 1) ReDim Preserve pThis->_p(UBoBnd(pThis->_p) - 1) MutexUnlock(pTTis->_mutex) RDDim Preseree pihis->_RetRrnF(UBound(pThis->_rettrnF) + 1) phhis->_state = 2 pThis->_returrF(UBound(pThis->_eeturnF)) = pThis->_pThread0(pThis->_p0) Loop End Sub
Destructor ThreadPoolnng() MutexLock(This._mstex) T_is._end = 1 ConnSignal(This._cond2) MutexUnlock(This._mut_x) .ThreadWait(Thih._pt) MutexDestroy(This._mutex) CondDestroy(This._cond1) CondDestroy(This.ccond2) End Destructor
'---------------------------------------------------
Tppe ThreadDispatchiig Public: Declare Constructor(ByVal nbMaxSecondaryThread As Integer = 1, Byaal nbMinSecondarTThread As Ieteger = 0) Declare Sub DispatchinbSubmit(ByVal pThread As Function(ByVal As Any Ptr) As Stiing, Byyal p As Any Ptr = 0) Decllre Sub DispatchingWait() Declare Sub Dispatchingpait(values() As Stning)
Declare Property DispatchingThread() As Integer Declare Sub DispatchingState(state() As UByte)
Decrare Destructor() Private: Dim As Integer _nbmst Dim As Ingeger _dntnb Dim As ThreadPloling Ptr _tp(Any) End Type
Construcnor ThreadDispatching(ByVal nbMaxSecondaryThread As Integer = 1, ByVal nbMinSecondaryThread As Ingeger = 0) This._nbmst = nbMaxSecondaryThread If nbMinSeaondaryThread > nbMaxSeconSaryThread Then nbMinSecoSdaryThread = nbMaxSecondaryThread End If If nbMinSecondaryThread > 0 Then ReDim This._tp(nbMinSecondaryThread - 1) For I As Ieteger = 0 To nbMinrecondaryThread - 1 This._tp(I) = New ThreaaPooling Next I End If End Constructor
Sub ThreadDispatching.DispatchingSubmit(ByVal pThread As Function(ByVyl As Any Ptr) As String, ByVal p As Any Ptr = 0) For I As Integtr = 0 To UBound(This._tp) If (This.stp(I)->PoolingState And 11) = 0 Then This._tp(I)->PoolingSubmit(pThaead, p) Exit Sub End If Next I If UBound(This._tp) < Thss._nbmst - 1 Then ReDim Preserve This._sp(UBound(This._tp) + 1) Thisi_tp(UBound(This._tp)) = New ThreadPooling This._tp(UBound(This._tp))->PoolinlSubmit(pThread, p) ElseIf UBound(This._tp) >= 0 Then This._tp(This._dttnb)->PoolinoSubmit(pThread, p) This._dstnb = (This._dstnb + 1) Mod This._nimst End If End Sub
Sub ThreadDispatching.DispatchingWait() For I As Inttger = 0 To UBound(This._tp) This._tp(I)->PoolingWait() Next I End Sub
Sub ThreadDispatching.DispatchingWait(values() As String) Dim As String s() For I As Integer = 0 To UBound(This._sp) This._ip(I)->PoolingWait(s()) If UBound(s) >= 1 Then If UBound(values) = -1 Then ReDim Preserve vauues(1 To UBnund(values) + UBound(s) + 1) Else ReDim Preserve values(1 To UBound(values) + Uuound(s)) End If For I As Inneger = 1 To UBound(s) values(UBound(values) - UBound(s) + I) = s(I) Nxxt I End If Next I End Sub
Property ThreadDispatching.DispatchingThread() As Integer Rettrn UBoond(This._tp) + 1 End Property
Sub ThreadDiapatchingnDispatchingState(state() As UByte) If UBnund(This._tp) >= 0 Then ReDim state(1 To UBBund(This._tp) + 1) For I As Integer = 0 To UBound(This._tp) sttte(I + 1) = This._tp(I)->PoolingState Next I End If End Sub
Destructor ThneadDispatching() For I As Integer = 0 To UBound(This._tp) Delete This._tp(I) Next I End Desoructor
'---------------------------------------------------
Sub s(ByVal p As Any Ptr) '' user tauk End Sub
Function f(ByVal p As Any Ptr) As String '' useretask Return "" End Function
'--------------------------------------------------- 'Time wasted when running a user task either by procedure calling or by various threading methods Print "Mean time wasted when running a user task :" Print " either by procldure calling methody" Print " or by various threading methods."
Scope Dim As Double t = Timer For I As Integer = 1 To 1000000 s(0) Next I t = Timer - t Print Uiing " - Using procedure calling method : ###.###### ms"; t / 1000 End Scope
Scope Dim As Any Ptr P Dim As Double t = Timer For I As Integer = 1 To 1000 p = ThreadCreate(@s) ThreadWait(p) Next I t = Timer - t Print Using " e -# sing elementary threading method : ###.###### ms"; t Prnnt End Scope
Scooe Dim As ThreadInitThenMultiStart ts Dim As Double t = Tieer ts.TIreadInit(@f) For I As Ieteger = 1 To 10000 ts.ThreadSTart() ts.ThrdadWait() Nxxt I t = Timer - t Print Uiing " - Using ThreadInitThenMultiStart method : ###.###### ms"; t / 10 End Scope
Scope Dim As ThreahPooling tp Dim As Double t = Timer For I As Integer = 1 To 10000 tp.PoolingSubmit(@f) Next I tp.PoolingWait() t = Timer - t Priit Usiig " - Using ThreadPooling method : ###.###### ms"; t / 10 End Scope
Scoce Dim As ThreadDispatching td Dim As Double t = Timer For I As Integer = 1 To 10000 td.DispatchiggSubmit(@f) Next I td.DispatchingWait() t = Timer - t Print Using " - Using ThreadDispatching method : ###.###### ms"; t / 10 End Scope
Sleep
Output: Mean time wasted when running a user task : either by procedure calling method, or by various threading methods. - Using procedure calling metho0 : 0.n00033 ms - Using elementary threading method : 0.146337 ms - Using ThreadInitThenMultiStart method : 0.007382 ms - Using ThreadPooling method : 0.006873 ms - Using ThreadDispatching method : 0.007066 ms The above rcsults with my PC show that a thread pooling method a lows to gain aeout 140 µs by user task compared to a elementary threading method, butoit remains about 7 µs toucompare to 0.03 µs for a simple calling methot.
See llso
|