wth -- Thread time .vs Subroutine time

General FreeBASIC programming questions.
Post Reply
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: wth -- Thread time .vs Subroutine time

Post by fxm »

Proposing another Type : " ThreadPooling " (but only compatible with user subroutines and not user functions)

On each Type instance, the submitted user thread subroutines are immediately entered in a queue specific to the instance.
These buffered user thread subroutines are sequentially as soon as possible executed in the thread dedicated to the instance.

Methods:
  • PoolingSubmit : Enter a user thread subroutine in the queue.
    PoolingWait : Wait for full emptying of the queue.

Code: Select all

Type ThreadPooling
    Public:
        Declare Constructor()
        Declare Sub PoolingSubmit(Byval pThread As Sub(Byval As Any Ptr), Byval p As Any Ptr = 0)
        Declare Sub PoolingWait()
        
        Declare Property PoolingState() As Ubyte
        
        Declare Destructor()
    Private:
        Dim As Sub(Byval p As Any Ptr) _pThread0
        Dim As Any Ptr _p0
        Dim As Sub(Byval p As Any Ptr) _pThread(Any)
        Dim As Any Ptr _p(Any)
        Dim As Any Ptr _mutex1
        Dim As Any Ptr _mutex2
        Dim As Any Ptr _pt
        Dim As Byte _end
        Dim As Ubyte _state
        Declare Static Sub _Thread(Byval p As Any Ptr)
End Type

Constructor ThreadPooling()
    Redim This._pThread(0)
    Redim This._p(0)
    This._mutex1 = Mutexcreate()
    This._mutex2 = Mutexcreate()
    Mutexlock(This._mutex2)
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Constructor

Sub ThreadPooling.PoolingSubmit(Byval pThread As Sub(Byval As Any Ptr), Byval p As Any Ptr = 0)
    Mutexlock(This._mutex1)
    If Ubound(This._pThread) = 0 Then
        Mutexunlock(This._mutex2)
    End If
    Redim Preserve This._pThread(Ubound(This._pThread) + 1)
    This._pThread(Ubound(This._pThread)) = pThread
    Redim Preserve This._p(Ubound(This._p) + 1)
    This._p(Ubound(This._p)) = p
    Mutexunlock(This._mutex1)
    This._state = 1
End Sub

Sub ThreadPooling.PoolingWait()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    This._end = 0
    This._state = 0
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Sub

Property ThreadPooling.PoolingState() As Ubyte
    Return This._state
End Property

Sub ThreadPooling._Thread(Byval p As Any Ptr)
    Dim As ThreadPooling Ptr pThis = p
    Do
        Mutexlock(pThis->_mutex1)
        While Ubound(pThis->_pThread) = 0
            Mutexunlock(pThis->_mutex1)
            pThis->_state = 4
            Mutexlock(pThis->_mutex2)
            If pThis->_end = 1 And Ubound(pThis->_pThread) = 0 Then Exit Sub
            Mutexlock(pThis->_mutex1)
        Wend
        pThis->_pThread0 = pThis->_pThread(1)
        pThis->_p0 = pThis->_p(1)
        For I As Integer = 2 To Ubound(pThis->_pThread)
            pThis->_pThread(I - 1) = pThis->_pThread(I)
            pThis->_p(I - 1) = pThis->_p(I)
        Next I
        Redim Preserve pThis->_pThread(Ubound(pThis->_pThread) - 1)
        Redim Preserve pThis->_p(Ubound(pThis->_p) - 1)
        Mutexunlock(pThis->_mutex1)
        pThis->_state = 2
        pThis->_pThread0(pThis->_p0)
    Loop
End Sub

Destructor ThreadPooling()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    Mutexdestroy(This._mutex1)
    Mutexdestroy(This._mutex2)
End Destructor

'---------------------------------------------------

Sub Prnt (Byref s As String, Byval nb As Integer, Byval t As Integer)
    For I As Integer = 1 To nb
        Print s;
        Sleep t, 1
    Next I
End Sub

Sub UserCode1 (Byval p As Any Ptr)
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("1", 10, 200)
End Sub

Sub UserCode2 (Byval p As Any Ptr)
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("2", 10, 150)
End Sub

Sub UserCode3 (Byval p As Any Ptr)
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("3", 10, 200)
End Sub

Sub UserCode4 (Byval p As Any Ptr)
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("4", 10, 150)
End Sub

Sub UserCode5 (Byval p As Any Ptr)
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("5", 10, 150)
End Sub

Sub UserCode6 (Byval p As Any Ptr)
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("6", 10, 150)
End Sub

Dim As String sa = "  Sequence #a: "
Dim As String sb = "  Sequence #b: "

Dim As ThreadPooling t

t.PoolingSubmit(@UserCode1, @sa)
t.PoolingSubmit(@UserCode2)
t.PoolingSubmit(@UserCode3)
Print " Sequence #a of 3 subroutines fully submitted "
t.PoolingWait
Print
Print " Sequence #a completed"
Print

t.PoolingSubmit(@UserCode4, @sb)
t.PoolingSubmit(@UserCode5)
t.PoolingSubmit(@UserCode6)
Print " Sequence #b of 3 subroutines fully submitted "
t.PoolingWait
Print
Print " Sequence #b completed"
Print

Sleep
Sequence #a of 3 subroutines fully submitted
Sequence #a: 111111111122222222223333333333
Sequence #a completed

Sequence #b of 3 subroutines fully submitted
Sequence #b: 444444444455555555556666666666
Sequence #b completed
Note: If the processor starts very quickly its thread, the acknowledgement message of each sequence of 3 submissions may appear inserted after the first "1" (or "4") printed by the first user subroutine de la 1st (or 2nd) sequence.
That is not the case here.

Note: In this first version, the Type is compatible with only user subroutines, because the use of functions requires access to a queue of function return values whose interface is to be considered.
Last edited by fxm on Feb 12, 2021 16:02, edited 7 times in total.
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: wth -- Thread time .vs Subroutine time

Post by fxm »

Proposing another Type : " ThreadPooling " (continued)

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 to sample at any time the state of the internal thread pooling.
This property can also be used during the debugging phase (allowing in addition to identify the case of blocking in the user thread procedure running).

PoolingState:
  • 0 -> User thread subroutines sequence execution completed (after 'PoolingWait' acknowledge or new instance creation)
    1 -> Beginning of user thread subroutine sequence submitted but no still executing (after first 'PoolingSubmit')
    2 -> User thread subroutine running
    4 -> User thread subroutines sequence execution pending (for 'PoolingWait' acknowledge or new user thread subroutine submission)
Code example to visualize the different states.
The tempos ('Sleep x, 1') in code have been set on my PC to obtain the below display (maybe not the same result on another PC).

Code: Select all

Type ThreadPooling
    Public:
        Declare Constructor()
        Declare Sub PoolingSubmit(Byval pThread As Sub(Byval As Any Ptr), Byval p As Any Ptr = 0)
        Declare Sub PoolingWait()
        
        Declare Property PoolingState() As Ubyte
        
        Declare Destructor()
    Private:
        Dim As Sub(Byval p As Any Ptr) _pThread0
        Dim As Any Ptr _p0
        Dim As Sub(Byval p As Any Ptr) _pThread(Any)
        Dim As Any Ptr _p(Any)
        Dim As Any Ptr _mutex1
        Dim As Any Ptr _mutex2
        Dim As Any Ptr _pt
        Dim As Byte _end
        Dim As Ubyte _state
        Declare Static Sub _Thread(Byval p As Any Ptr)
End Type

Constructor ThreadPooling()
    Redim This._pThread(0)
    Redim This._p(0)
    This._mutex1 = Mutexcreate()
    This._mutex2 = Mutexcreate()
    Mutexlock(This._mutex2)
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Constructor

Sub ThreadPooling.PoolingSubmit(Byval pThread As Sub(Byval As Any Ptr), Byval p As Any Ptr = 0)
    Mutexlock(This._mutex1)
    If Ubound(This._pThread) = 0 Then
        Mutexunlock(This._mutex2)
    End If
    Redim Preserve This._pThread(Ubound(This._pThread) + 1)
    This._pThread(Ubound(This._pThread)) = pThread
    Redim Preserve This._p(Ubound(This._p) + 1)
    This._p(Ubound(This._p)) = p
    Mutexunlock(This._mutex1)
    This._state = 1
End Sub

Sub ThreadPooling.PoolingWait()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    This._end = 0
    This._state = 0
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Sub

Property ThreadPooling.PoolingState() As Ubyte
    Return This._state
End Property

Sub ThreadPooling._Thread(Byval p As Any Ptr)
    Dim As ThreadPooling Ptr pThis = p
    Do
        Mutexlock(pThis->_mutex1)
        While Ubound(pThis->_pThread) = 0
            Mutexunlock(pThis->_mutex1)
            pThis->_state = 4
            Mutexlock(pThis->_mutex2)
            If pThis->_end = 1 And Ubound(pThis->_pThread) = 0 Then Exit Sub
            Mutexlock(pThis->_mutex1)
        Wend
        pThis->_pThread0 = pThis->_pThread(1)
        pThis->_p0 = pThis->_p(1)
        For I As Integer = 2 To Ubound(pThis->_pThread)
            pThis->_pThread(I - 1) = pThis->_pThread(I)
            pThis->_p(I - 1) = pThis->_p(I)
        Next I
        Redim Preserve pThis->_pThread(Ubound(pThis->_pThread) - 1)
        Redim Preserve pThis->_p(Ubound(pThis->_p) - 1)
        Mutexunlock(pThis->_mutex1)
        pThis->_state = 2
        pThis->_pThread0(pThis->_p0)
    Loop
End Sub

Destructor ThreadPooling()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    Mutexdestroy(This._mutex1)
    Mutexdestroy(This._mutex2)
End Destructor

'---------------------------------------------------

Sub UserCode (Byval p As Any Ptr)
    Sleep 100, 1
    Print " UserThreadT procedure body is running"
    Sleep 100, 1
    Print " UserThreadT procedure body is no longer running"
End Sub

Dim As String sa = "  Sequence #a: "
Dim As String sb = "  Sequence #b: "

Dim As ThreadPooling t
Print "PoolingState = " & t.PoolingState
Print

t.PoolingSubmit(@UserCode)
t.PoolingSubmit(@UserCode)
Print "PoolingState = " & t.PoolingState
sleep 50, 1
Print " Sequence #a submitted "
Print "PoolingState = " & t.PoolingState
sleep 450, 1
Print "PoolingState = " & t.PoolingState
Print " Sequence #a ended and pending"
t.PoolingWait
Print "PoolingState = " & t.PoolingState
Print " Sequence #a completed and acknowledged"
Print

t.PoolingSubmit(@UserCode)
t.PoolingSubmit(@UserCode)
Print "PoolingState = " & t.PoolingState
sleep 50, 1
Print " Sequence #b submitted "
Print "PoolingState = " & t.PoolingState
sleep 450, 1
Print "PoolingState = " & t.PoolingState
Print " Sequence #b ended and pending"
t.PoolingWait
Print "PoolingState = " & t.PoolingState
Print " Sequence #b completed and acknowledged"
Print

Sleep

Code: Select all

PoolingState = 0

PoolingState = 1
 Sequence #a submitted
PoolingState = 2
 UserThreadT procedure body is running
 UserThreadT procedure body is no longer running
 UserThreadT procedure body is running
 UserThreadT procedure body is no longer running
PoolingState = 4
 Sequence #a ended and pending
PoolingState = 0
 Sequence #a completed and acknowledged

PoolingState = 1
 Sequence #b submitted
PoolingState = 2
 UserThreadT procedure body is running
 UserThreadT procedure body is no longer running
 UserThreadT procedure body is running
 UserThreadT procedure body is no longer running
PoolingState = 4
 Sequence #b ended and pending
PoolingState = 0
 Sequence #b completed and acknowledged
 
Last edited by fxm on Feb 12, 2021 16:05, edited 7 times in total.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: wth -- Thread time .vs Subroutine time

Post by deltarho[1859] »

@fxm

I don't see the point of your 'ThreadPooling' Type.

With ThreadInitThenMultiStart I can do, for example, three jobs in serial processing or with ThreadPooling I can do three jobs in serial processing.

What is the difference in practice? OK, programmatically ThreadPooling is easier if we wanted the three jobs to be executed one after the other, but we have more control with ThreadInitThenMultiStart as we can decide when the jobs get executed; including one after the other if that is what we wanted.

The danger of having several techniques is with a user new to threading wondering which technique to use.
Lost Zergling
Posts: 534
Joined: Dec 02, 2011 22:51
Location: France

Re: wth -- Thread time .vs Subroutine time

Post by Lost Zergling »

There was a controversy with adeyeblue to whom I had provided an opinion in terms of architecture and design, but this empirical answer, however logical, had left an experimental uncertainty. It is indeed possible to control several threads with a single object and 3 or 4 mutexes, while retaining a design that is simple to use, but at the cost of fairly severe functional losses on which it is not useful to extend. From a technical point of view, it is instructive because it seems to confirm that it is indeed the object structure and not the array which actually seems to allow the isolation of the thread sequence, even tought parallelized. I shall investigate these techniques also, may need some time. To this point, multistart, wich is really versatile, seems of course better excepted perhaps for some specific use cases.
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: wth -- Thread time .vs Subroutine time

Post by fxm »

Proposal of improved type: " ThreadPooling " (supporting now the user thread functions)
(maybe the last post on this Type)

This 'ThreadPooling' Type revision now supports the user thread functions (as for the 'ThreadInitThenMultiStart' Type).

An overload method 'PoolingWait(values() As String)' is added.
PoolingWait(values() As String): Fill 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)
Note: The other overload method 'PoolingWait()' (without passed parameter) also clears the internal return values.

Code: Select all

Type ThreadPooling
    Public:
        Declare Constructor()
        Declare Sub PoolingSubmit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
        Declare Sub PoolingWait()
        Declare Sub PoolingWait(values() As String)
        
        Declare Property PoolingState() As Ubyte
        
        Declare Destructor()
    Private:
        Dim As Function(Byval p As Any Ptr) As String _pThread0
        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 _mutex1
        Dim As Any Ptr _mutex2
        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 Type

Constructor ThreadPooling()
    Redim This._pThread(0)
    Redim This._p(0)
    Redim This._returnF(0)
    This._mutex1 = Mutexcreate()
    This._mutex2 = Mutexcreate()
    Mutexlock(This._mutex2)
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Constructor

Sub ThreadPooling.PoolingSubmit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
    Mutexlock(This._mutex1)
    If Ubound(This._pThread) = 0 Then
        Mutexunlock(This._mutex2)
    End If
    Redim Preserve This._pThread(Ubound(This._pThread) + 1)
    This._pThread(Ubound(This._pThread)) = pThread
    Redim Preserve This._p(Ubound(This._p) + 1)
    This._p(Ubound(This._p)) = p
    Mutexunlock(This._mutex1)
    This._state = 1
End Sub

Sub ThreadPooling.PoolingWait()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    This._end = 0
    Redim This._returnF(0)
    This._state = 0
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Sub

Sub ThreadPooling.PoolingWait(values() As String)
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    This._end = 0
    If Ubound(This._returnF) > 0 Then
        Redim values(1 To Ubound(This._returnF))
        For I As Integer = 1 To Ubound(This._returnF)
            values(I) = This._returnF(I)
        Next I
        Redim This._returnF(0)
    Else
        Erase values
    End If
    This._state = 0
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Sub

Property ThreadPooling.PoolingState() As Ubyte
    Return This._state
End Property

Sub ThreadPooling._Thread(Byval p As Any Ptr)
    Dim As ThreadPooling Ptr pThis = p
    Do
        Mutexlock(pThis->_mutex1)
        While Ubound(pThis->_pThread) = 0
            Mutexunlock(pThis->_mutex1)
            pThis->_state = 4
            Mutexlock(pThis->_mutex2)
            If pThis->_end = 1 And Ubound(pThis->_pThread) = 0 Then Exit Sub
            Mutexlock(pThis->_mutex1)
        Wend
        pThis->_pThread0 = pThis->_pThread(1)
        pThis->_p0 = pThis->_p(1)
        For I As Integer = 2 To Ubound(pThis->_pThread)
            pThis->_pThread(I - 1) = pThis->_pThread(I)
            pThis->_p(I - 1) = pThis->_p(I)
        Next I
        Redim Preserve pThis->_pThread(Ubound(pThis->_pThread) - 1)
        Redim Preserve pThis->_p(Ubound(pThis->_p) - 1)
        Mutexunlock(pThis->_mutex1)
        Redim Preserve pThis->_ReturnF(Ubound(pThis->_returnF) + 1)
        pThis->_state = 2
        pThis->_returnF(Ubound(pThis->_returnF)) = pThis->_pThread0(pThis->_p0)
    Loop
End Sub

Destructor ThreadPooling()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    Mutexdestroy(This._mutex1)
    Mutexdestroy(This._mutex2)
End Destructor

'---------------------------------------------------

Sub Prnt (Byref s As String, Byval nb As Integer, Byval t As Integer)
    For I As Integer = 1 To nb
        Print s;
        Sleep t, 1
    Next I
End Sub

Function UserCode1 (Byval p As Any Ptr) As String
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("1", 10, 200)
    Return "UserCode1"
End Function

Function UserCode2 (Byval p As Any Ptr) As String
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("2", 10, 150)
    Return "UserCode2"
End Function

Function UserCode3 (Byval p As Any Ptr) As String
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("3", 10, 200)
    Return "UserCode3"
End Function

Function UserCode4 (Byval p As Any Ptr) As String
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("4", 10, 150)
    Return "UserCode4"
End Function

Function UserCode5 (Byval p As Any Ptr) As String
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("5", 10, 150)
    Return "UserCode5"
End Function

Function UserCode6 (Byval p As Any Ptr) As String
    Dim As String Ptr ps = p
    If ps > 0 Then Print *ps;
    Prnt("6", 10, 150)
    Return "UserCode6"
End Function

Dim As String sa = "  Sequence #a: "
Dim As String sb = "  Sequence #b: "
Dim As String s()

Dim As ThreadPooling t

t.PoolingSubmit(@UserCode1, @sa)
t.PoolingSubmit(@UserCode2)
t.PoolingSubmit(@UserCode3)
Print " Sequence #a of 3 functions fully submitted "
t.PoolingWait
Print
Print " Sequence #a completed"
Print
t.Destructor()
t.Constructor()

t.PoolingSubmit(@UserCode4, @sb)
t.PoolingSubmit(@UserCode5)
t.PoolingSubmit(@UserCode6)
Print " Sequence #b of 3 functions fully submitted "
t.PoolingWait(s())
Print
Print " Sequence #b completed"
Print

Print " List of returned values"
For I As Integer = Lbound(s) To Ubound(s)
    Print "  " & I & ": " & s(I)
Next I
Print

Sleep
Sequence #a of 3 functions fully submitted
Sequence #a: 111111111122222222223333333333
Sequence #a completed

Sequence #b of 3 functions fully submitted
Sequence #b: 444444444455555555556666666666
Sequence #b completed

List of returned values
1: UserCode4
2: UserCode5
3: UserCode6
Last edited by fxm on Feb 12, 2021 16:07, edited 2 times in total.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: wth -- Thread time .vs Subroutine time

Post by deltarho[1859] »

It worth remembering that the examples given are not how to use threaded code. We do not wait immediately after invoking secondary threads of execution otherwise the primary thread would be suspended.

We would normally invoke the secondary threads via serial processing or parallel processing or both and then follow that with the primary thread of execution; whether we use 'ThreadPooling' or 'ThreadInitThenMultiStart'.

When the primary thread has completed its task it is at that point we wait. If all the secondary tasks have completed, then we 'fall through' the wait and continue with the primary thread. If the secondary threads have yet to complete then the wait will stop the primary thread continuing until the secondary threads complete.

We could invoke further secondary threads and repeat the above behaviour.

We should only use serial processing when all the available CPU cores have been utilized. Since I have a quad-core with hyperthreading I would not have used serial processing with any of the examples above but would have used parallel processing for all the UserCodes. It may be worthwhile to have a primary thread and three Subs using serial processing in a secondary thread with a dual-core CPU. It would be a waste of resources to do that with a quad-core. In that case, it would be better to have a primary thread and the three Subs running in their own secondary threads; using four threads in total and wait for the three secondary threads to complete before proceeding with the primary thread.

I am not a 'power user' when it comes to threading and have never had to resort to serial processing in a thread.
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: wth -- Thread time .vs Subroutine time

Post by fxm »

The test of 'ThreadState = 4' (or 'PoolingState = 4') can allow to detected that the user thread procedure (or sequence of user thread procedures) has completed and that an acknowledgement can now be executed without waiting via 'ThreadWait' (or 'PoolingWait').
So if the value is not yet 4, another one-off task can be inserted instead of waiting without doing anything.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: wth -- Thread time .vs Subroutine time

Post by deltarho[1859] »

fxm wrote:So if the value is not yet 4, another one-off task can be inserted instead of waiting without doing anything.
OK, but I have never found myself in that position. My primary threads have always got to a point where they cannot proceed any further without the secondary threads completing, so I wait and either 'fall through' or not as the case may be.

If I wanted to 'sneak' another task in then clearly that would not be dependent upon the secondary threads completing, so I would probably execute that task and then wait; increasing the likelihood of a 'fall through'. A well-designed application shouldn't need to check thread states. Thread states would come into their own, in my opinion, for debugging purposes. Of course, the 'sneaked in' task could be a candidate for threading itself.
Lost Zergling
Posts: 534
Joined: Dec 02, 2011 22:51
Location: France

Re: wth -- Thread time .vs Subroutine time

Post by Lost Zergling »

'ThreadState = 1-3' are close to syntactic sugar. On the contrary 'ThreadState = 4' is usefull feature : imagine one wants to paralellize a one repetitive but synchronized task (ie : filling / mapping datas in memory). First step is to make in memory structure 'thread safe'. Then you can imagine using a loop an a token within several multistart, assuming that each task could compute pretty same time, so filling the structure by synchronous sequences of blocs of asynchronous threads (2/4/more), waiting for all asynch between each synch step. MultiStart can do this, but ThreadState = 4 would allow to check if one thread is available, freeing from the synchronous sequence, the time testing status negligeable compare to time lost because of differenciated execution time inside blocks*number of blocks. I like much more multistart because its granularity is better at lesser cost, allowing more flexibility knowing that flexibility is or shall be a major feature. In comparison, up to my opinion, to achieve such job (or related), threadpooling would be too specialized and not enought flexible and would probably require new conceptual compromise or heavy patch, wich would lead to a discouraging complexity.
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: wth -- Thread time .vs Subroutine time

Post by fxm »

The main advantage of 'ThreadPooling' compared to the 'ThreadInitThenMultiStart' is a simpler code when it comes to chaining user thread procedures.

Example of 4 user procedures executed with 1 thread:
Dim As ThreadPooling tb
tb.PoolingSubmit(@UserCode1)
tb.PoolingSubmit(@UserCode2)
tb.PoolingSubmit(@UserCode3)
tb.PoolingSubmit(@UserCode4)
tb.PoolingWait()
Dim As ThreadInitThenMultiStart ta
ta.ThreadInit(@UserCode1)
ta.ThreadStart()
ta.ThreadWait()
ta.ThreadInit(@UserCode2)
ta.ThreadStart()
ta.ThreadWait()
ta.ThreadInit(@UserCode3)
ta.ThreadStart()
ta.ThreadWait()
ta.ThreadInit(@UserCode4)
ta.ThreadStart()
ta.ThreadWait()
Example of 4 user procedures executed with 2 threads:
Dim As ThreadPooling tb1, tb2
tb1.PoolingSubmit(@UserCode1)
tb2.PoolingSubmit(@UserCode2)
tb1.PoolingSubmit(@UserCode3)
tb2.PoolingSubmit(@UserCode4)
tb1.PoolingWait()
tb2.PoolingWait()
Dim As ThreadInitThenMultiStart ta1, ta2
ta1.ThreadInit(@UserCode1)
ta2.ThreadInit(@UserCode2)
ta1.ThreadStart()
ta2.ThreadStart()
ta1.ThreadWait()
ta2.ThreadWait()
ta1.ThreadInit(@UserCode3)
ta2.ThreadInit(@UserCode4)
ta1.ThreadStart()
ta2.ThreadStart()
ta1.ThreadWait()
ta2.ThreadWait()
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: wth -- Thread time .vs Subroutine time

Post by deltarho[1859] »

fxm wrote:The main advantage of 'ThreadPooling' compared to the 'ThreadInitThenMultiStart' is a simpler code when it comes to chaining user thread procedures.
I recognized that earlier when I wrote: "OK, programmatically ThreadPooling is easier if we wanted the three jobs to be executed one after the other,..."

With my 4 cores/4 threads I could have

Code: Select all

Dim As ThreadPooling t(1 to 7)
t(1).PoolingSubmit(@UserCode1)
t(2).PoolingSubmit(@UserCode2)
t(3).PoolingSubmit(@UserCode3)
t(4).PoolingSubmit(@UserCode4)
t(5).PoolingSubmit(@UserCode5)
t(6).PoolingSubmit(@UserCode6)
t(7).PoolingSubmit(@UserCode7)
'
'
' Primary thread
'
'
t(1).PoolingWait()
t(2).PoolingWait()
t(3).PoolingWait()
t(4).PoolingWait()
t(5).PoolingWait()
t(6).PoolingWait()
t(7).PoolingWait()
'
'
' Primary thread
'
'
and no serial processing. Image
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: wth -- Thread time .vs Subroutine time

Post by fxm »

Example of 8 user codes executed with 1, 2, 4, or 8 secondary threads:

Code: Select all

Type ThreadInitThenMultiStart
    Public:
        Declare Constructor()
        Declare Sub ThreadInit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
        Declare Sub ThreadStart()
        Declare Sub ThreadStart(Byval p As Any Ptr)
        Declare Function ThreadWait() As String
        
        Declare Property ThreadState() As Ubyte
        
        Declare Destructor()
    Private:
        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 _mutex2
        Dim As Any Ptr _mutex3
        Dim As Any Ptr _pt
        Dim As Byte _end
        Dim As String _returnF
        Dim As Ubyte _state
        Declare Static Sub _Thread(Byval p As Any Ptr)
End Type

Constructor ThreadInitThenMultiStart()
    This._mutex1 = Mutexcreate()
    Mutexlock(This._mutex1)
    This._mutex2 = Mutexcreate()
    Mutexlock(This._mutex2)
    This._mutex3 = Mutexcreate()
    Mutexlock(This._mutex3)
End Constructor

Sub ThreadInitThenMultiStart.ThreadInit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
    This._pThread = pThread
    This._p = p
    If This._pt = 0 Then
        This._pt= Threadcreate(@ThreadInitThenMultiStart._Thread, @This)
        Mutexunlock(This._mutex3)
        This._state = 1
    End If
End Sub

Sub ThreadInitThenMultiStart.ThreadStart()
    Mutexlock(This._mutex3)
    Mutexunlock(This._mutex1)
End Sub

Sub ThreadInitThenMultiStart.ThreadStart(Byval p As Any Ptr)
    Mutexlock(This._mutex3)
    This._p = p
    Mutexunlock(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 Ubyte
    Return This._state
End Property

Sub ThreadInitThenMultiStart._Thread(Byval p As Any Ptr)
    Dim As ThreadInitThenMultiStart Ptr pThis = p
    Do
        Mutexlock(pThis->_mutex1)
        If pThis->_end = 1 Then Exit Sub
        pThis->_state = 2
        pThis->_returnF = pThis->_pThread(pThis->_p)
        pThis->_state = 4
        Mutexunlock(pThis->_mutex2)
    Loop
End Sub

Destructor ThreadInitThenMultiStart()
    If This._pt > 0 Then
        This._end = 1
        Mutexunlock(This._mutex1)
        .ThreadWait(This._pt)
    End If
    Mutexdestroy(This._mutex1)
    Mutexdestroy(This._mutex2)
    Mutexdestroy(This._mutex3)
End Destructor

'---------------------------------------------------

Type ThreadPooling
    Public:
        Declare Constructor()
        Declare Sub PoolingSubmit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
        Declare Sub PoolingWait()
        Declare Sub PoolingWait(values() As String)
        
        Declare Property PoolingState() As Ubyte
        
        Declare Destructor()
    Private:
        Dim As Function(Byval p As Any Ptr) As String _pThread0
        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 _mutex1
        Dim As Any Ptr _mutex2
        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 Type

Constructor ThreadPooling()
    Redim This._pThread(0)
    Redim This._p(0)
    Redim This._returnF(0)
    This._mutex1 = Mutexcreate()
    This._mutex2 = Mutexcreate()
    Mutexlock(This._mutex2)
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Constructor

Sub ThreadPooling.PoolingSubmit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
    Mutexlock(This._mutex1)
    If Ubound(This._pThread) = 0 Then
        Mutexunlock(This._mutex2)
    End If
    Redim Preserve This._pThread(Ubound(This._pThread) + 1)
    This._pThread(Ubound(This._pThread)) = pThread
    Redim Preserve This._p(Ubound(This._p) + 1)
    This._p(Ubound(This._p)) = p
    Mutexunlock(This._mutex1)
    This._state = 1
End Sub

Sub ThreadPooling.PoolingWait()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    This._end = 0
    Redim This._returnF(0)
    This._state = 0
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Sub

Sub ThreadPooling.PoolingWait(values() As String)
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    This._end = 0
    If Ubound(This._returnF) > 0 Then
        Redim values(1 To Ubound(This._returnF))
        For I As Integer = 1 To Ubound(This._returnF)
            values(I) = This._returnF(I)
        Next I
        Redim This._returnF(0)
    Else
        Erase values
    End If
    This._state = 0
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Sub

Property ThreadPooling.PoolingState() As Ubyte
    Return This._state
End Property

Sub ThreadPooling._Thread(Byval p As Any Ptr)
    Dim As ThreadPooling Ptr pThis = p
    Do
        Mutexlock(pThis->_mutex1)
        While Ubound(pThis->_pThread) = 0
            Mutexunlock(pThis->_mutex1)
            pThis->_state = 4
            Mutexlock(pThis->_mutex2)
            If pThis->_end = 1 And Ubound(pThis->_pThread) = 0 Then Exit Sub
            Mutexlock(pThis->_mutex1)
        Wend
        pThis->_pThread0 = pThis->_pThread(1)
        pThis->_p0 = pThis->_p(1)
        For I As Integer = 2 To Ubound(pThis->_pThread)
            pThis->_pThread(I - 1) = pThis->_pThread(I)
            pThis->_p(I - 1) = pThis->_p(I)
        Next I
        Redim Preserve pThis->_pThread(Ubound(pThis->_pThread) - 1)
        Redim Preserve pThis->_p(Ubound(pThis->_p) - 1)
        Mutexunlock(pThis->_mutex1)
        Redim Preserve pThis->_ReturnF(Ubound(pThis->_returnF) + 1)
        pThis->_state = 2
        pThis->_returnF(Ubound(pThis->_returnF)) = pThis->_pThread0(pThis->_p0)
    Loop
End Sub

Destructor ThreadPooling()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    Mutexdestroy(This._mutex1)
    Mutexdestroy(This._mutex2)
End Destructor

'---------------------------------------------------

Function UserCode1 (Byval p As Any Ptr) As String
    For I As Integer = 1 To 9
        Print "1";
        Sleep 100, 1
    Next I
    Return ""
End Function

Function UserCode2 (Byval p As Any Ptr) As String
    For I As Integer = 1 To 9
        Print "2";
        Sleep 100, 1
    Next I
    Return ""
End Function

Function UserCode3 (Byval p As Any Ptr) As String
    For I As Integer = 1 To 9
        Print "3";
        Sleep 100, 1
    Next I
    Return ""
End Function

Function UserCode4 (Byval p As Any Ptr) As String
    For I As Integer = 1 To 9
        Print "4";
        Sleep 100, 1
    Next I
    Return ""
End Function

Function UserCode5 (Byval p As Any Ptr) As String
    For I As Integer = 1 To 9
        Print "5";
        Sleep 100, 1
    Next I
    Return ""
End Function

Function UserCode6 (Byval p As Any Ptr) As String
    For I As Integer = 1 To 9
        Print "6";
        Sleep 100, 1
    Next I
    Return ""
End Function

Function UserCode7 (Byval p As Any Ptr) As String
    For I As Integer = 1 To 9
        Print "7";
        Sleep 100, 1
    Next I
    Return ""
End Function

Function UserCode8 (Byval p As Any Ptr) As String
    For I As Integer = 1 To 9
        Print "8";
        Sleep 100, 1
    Next I
    Return ""
End Function

'---------------------------------------------------

Dim As ThreadInitThenMultiStart ta1, ta2, ta3, ta4, ta5, ta6, ta7, ta8
Dim As ThreadPooling tb1, tb2, tb3, tb4, tb5, tb6, tb7, tb8

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 1 secondary thread:"
Print "    ";
ta1.ThreadInit(@UserCode1)
ta1.ThreadStart()
ta1.ThreadWait()
ta1.ThreadInit(@UserCode2)
ta1.ThreadStart()
ta1.ThreadWait()
ta1.ThreadInit(@UserCode3)
ta1.ThreadStart()
ta1.ThreadWait()
ta1.ThreadInit(@UserCode4)
ta1.ThreadStart()
ta1.ThreadWait()
ta1.ThreadInit(@UserCode5)
ta1.ThreadStart()
ta1.ThreadWait()
ta1.ThreadInit(@UserCode6)
ta1.ThreadStart()
ta1.ThreadWait()
ta1.ThreadInit(@UserCode7)
ta1.ThreadStart()
ta1.ThreadWait()
ta1.ThreadInit(@UserCode8)
ta1.ThreadStart()
ta1.ThreadWait()
Print

'---------------------------------------------------

Print "'ThreadPooling' with 1 secondary thread:"
Print "    ";
tb1.PoolingSubmit(@UserCode1)
tb1.PoolingSubmit(@UserCode2)
tb1.PoolingSubmit(@UserCode3)
tb1.PoolingSubmit(@UserCode4)
tb1.PoolingSubmit(@UserCode5)
tb1.PoolingSubmit(@UserCode6)
tb1.PoolingSubmit(@UserCode7)
tb1.PoolingSubmit(@UserCode8)
tb1.PoolingWait()
Print
Print

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 2 secondary threads:"
Print "    ";
ta1.ThreadInit(@UserCode1)
ta2.ThreadInit(@UserCode2)
ta1.ThreadStart()
ta2.ThreadStart()
ta1.ThreadWait()
ta2.ThreadWait()
ta1.ThreadInit(@UserCode3)
ta2.ThreadInit(@UserCode4)
ta1.ThreadStart()
ta2.ThreadStart()
ta1.ThreadWait()
ta2.ThreadWait()
ta1.ThreadInit(@UserCode5)
ta2.ThreadInit(@UserCode6)
ta1.ThreadStart()
ta2.ThreadStart()
ta1.ThreadWait()
ta2.ThreadWait()
ta1.ThreadInit(@UserCode7)
ta2.ThreadInit(@UserCode8)
ta1.ThreadStart()
ta2.ThreadStart()
ta1.ThreadWait()
ta2.ThreadWait()
Print

'---------------------------------------------------

Print "'ThreadPooling' with 2 secondary threads:"
Print "    ";
tb1.PoolingSubmit(@UserCode1)
tb2.PoolingSubmit(@UserCode2)
tb1.PoolingSubmit(@UserCode3)
tb2.PoolingSubmit(@UserCode4)
tb1.PoolingSubmit(@UserCode5)
tb2.PoolingSubmit(@UserCode6)
tb1.PoolingSubmit(@UserCode7)
tb2.PoolingSubmit(@UserCode8)
tb1.PoolingWait()
tb2.PoolingWait()
Print
Print

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 4 secondary threads:"
Print "    ";
ta1.ThreadInit(@UserCode1)
ta2.ThreadInit(@UserCode2)
ta3.ThreadInit(@UserCode3)
ta4.ThreadInit(@UserCode4)
ta1.ThreadStart()
ta2.ThreadStart()
ta3.ThreadStart()
ta4.ThreadStart()
ta1.ThreadWait()
ta2.ThreadWait()
ta3.ThreadWait()
ta4.ThreadWait()
ta1.ThreadInit(@UserCode5)
ta2.ThreadInit(@UserCode6)
ta3.ThreadInit(@UserCode7)
ta4.ThreadInit(@UserCode8)
ta1.ThreadStart()
ta2.ThreadStart()
ta3.ThreadStart()
ta4.ThreadStart()
ta1.ThreadWait()
ta2.ThreadWait()
ta3.ThreadWait()
ta4.ThreadWait()
Print

'---------------------------------------------------

Print "'ThreadPooling' with 4 secondary threads:"
Print "    ";
tb1.PoolingSubmit(@UserCode1)
tb2.PoolingSubmit(@UserCode2)
tb3.PoolingSubmit(@UserCode3)
tb4.PoolingSubmit(@UserCode4)
tb1.PoolingSubmit(@UserCode5)
tb2.PoolingSubmit(@UserCode6)
tb3.PoolingSubmit(@UserCode7)
tb4.PoolingSubmit(@UserCode7)
tb1.PoolingWait()
tb2.PoolingWait()
tb3.PoolingWait()
tb4.PoolingWait()
Print
Print

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 8 secondary threads:"
Print "    ";
ta1.ThreadInit(@UserCode1)
ta2.ThreadInit(@UserCode2)
ta3.ThreadInit(@UserCode3)
ta4.ThreadInit(@UserCode4)
ta5.ThreadInit(@UserCode5)
ta6.ThreadInit(@UserCode6)
ta7.ThreadInit(@UserCode7)
ta8.ThreadInit(@UserCode8)
ta1.ThreadStart()
ta2.ThreadStart()
ta3.ThreadStart()
ta4.ThreadStart()
ta5.ThreadStart()
ta6.ThreadStart()
ta7.ThreadStart()
ta8.ThreadStart()
ta1.ThreadWait()
ta2.ThreadWait()
ta3.ThreadWait()
ta4.ThreadWait()
ta5.ThreadWait()
ta6.ThreadWait()
ta7.ThreadWait()
ta8.ThreadWait()
Print

'---------------------------------------------------

Print "'ThreadPooling' with 8 secondary threads:"
Print "    ";
tb1.PoolingSubmit(@UserCode1)
tb2.PoolingSubmit(@UserCode2)
tb3.PoolingSubmit(@UserCode3)
tb4.PoolingSubmit(@UserCode4)
tb5.PoolingSubmit(@UserCode5)
tb6.PoolingSubmit(@UserCode6)
tb7.PoolingSubmit(@UserCode7)
tb8.PoolingSubmit(@UserCode8)
tb1.PoolingWait()
tb2.PoolingWait()
tb3.PoolingWait()
tb4.PoolingWait()
tb5.PoolingWait()
tb6.PoolingWait()
tb7.PoolingWait()
tb8.PoolingWait()
Print
Print

Sleep
'ThreadInitThenMultiStart' with 1 secondary thread:
111111111222222222333333333444444444555555555666666666777777777888888888
'ThreadPooling' with 1 secondary thread:
111111111222222222333333333444444444555555555666666666777777777888888888

'ThreadInitThenMultiStart' with 2 secondary threads:
121221211221122112343443343434343443565665565656655665787887787878877887
'ThreadPooling' with 2 secondary threads:
121212212121212121433434434334433434656565656565565665788787878778788787

'ThreadInitThenMultiStart' with 4 secondary threads:
123442313124421313244231134212434321567867588567675868577568856756788675
'ThreadPooling' with 4 secondary threads:
234141233124213423143412421341232314757665777657576757677675757665777756

'ThreadInitThenMultiStart' with 8 secondary threads:
124356786875342114235786678532411342578668725431145327861486275353267841
'ThreadPooling' with 8 secondary threads:
231456785768413223146785578641235324168787164235524631873718624554682173
Each user code takes about 1 second:
- on my PC, using 1 secondary thread, about 8 seconds in all,
- on my PC, using 2 secondary threads, about 4 seconds in all,
- on my PC, using 4 secondary threads, about 2 seconds in all,
- on my PC, using 8 secondary threads, about 1 second in all.
Last edited by fxm on Feb 12, 2021 21:15, edited 2 times in total.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: wth -- Thread time .vs Subroutine time

Post by deltarho[1859] »

@fxm

I am being pedantic but 8 user codes and 8 threads requires 9 threads, 8 secondary and one primary, for full parallelization. Have you got 9 plus cores? That is why I used 7 secondary threads in my last post.

Anyway, as I said I am being pedantic.

Your timings show the benefit of keeping serial processing down to a minimum. In other words don't start using serial processing until you have hit your core/thread limit. In my case, I have never put my 4 core/4 thread machine under any pressure yet. I hadn't thought about this before but when CryptoRndII opens it uses a primary and four secondary threads, but it doesn't take long to drop to a primary and two secondary threads. Anyone with a dual-core machine may think that would spoil CryptoRndII's throughput but the likelihood of waiting for the upcoming buffer to become available will need the current buffer to be exhausted rapidly and that is unlikely in practice; the requested random numbers are being used to do something, not just requested.
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: wth -- Thread time .vs Subroutine time

Post by fxm »

You are right !
I corrected my above post, replacing "thread(s)' with "secondary thread(s)" everywhere.
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: wth -- Thread time .vs Subroutine time

Post by fxm »

With my PC (Intel Core i7), I tested up to 32 secondary threads, and I still find a linear gain !

Code: Select all

Type ThreadInitThenMultiStart
    Public:
        Declare Constructor()
        Declare Sub ThreadInit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
        Declare Sub ThreadStart()
        Declare Sub ThreadStart(Byval p As Any Ptr)
        Declare Function ThreadWait() As String
        
        Declare Property ThreadState() As Ubyte
        
        Declare Destructor()
    Private:
        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 _mutex2
        Dim As Any Ptr _mutex3
        Dim As Any Ptr _pt
        Dim As Byte _end
        Dim As String _returnF
        Dim As Ubyte _state
        Declare Static Sub _Thread(Byval p As Any Ptr)
End Type

Constructor ThreadInitThenMultiStart()
    This._mutex1 = Mutexcreate()
    Mutexlock(This._mutex1)
    This._mutex2 = Mutexcreate()
    Mutexlock(This._mutex2)
    This._mutex3 = Mutexcreate()
    Mutexlock(This._mutex3)
End Constructor

Sub ThreadInitThenMultiStart.ThreadInit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
    This._pThread = pThread
    This._p = p
    If This._pt = 0 Then
        This._pt= Threadcreate(@ThreadInitThenMultiStart._Thread, @This)
        Mutexunlock(This._mutex3)
        This._state = 1
    End If
End Sub

Sub ThreadInitThenMultiStart.ThreadStart()
    Mutexlock(This._mutex3)
    Mutexunlock(This._mutex1)
End Sub

Sub ThreadInitThenMultiStart.ThreadStart(Byval p As Any Ptr)
    Mutexlock(This._mutex3)
    This._p = p
    Mutexunlock(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 Ubyte
    Return This._state
End Property

Sub ThreadInitThenMultiStart._Thread(Byval p As Any Ptr)
    Dim As ThreadInitThenMultiStart Ptr pThis = p
    Do
        Mutexlock(pThis->_mutex1)
        If pThis->_end = 1 Then Exit Sub
        pThis->_state = 2
        pThis->_returnF = pThis->_pThread(pThis->_p)
        pThis->_state = 4
        Mutexunlock(pThis->_mutex2)
    Loop
End Sub

Destructor ThreadInitThenMultiStart()
    If This._pt > 0 Then
        This._end = 1
        Mutexunlock(This._mutex1)
        .ThreadWait(This._pt)
    End If
    Mutexdestroy(This._mutex1)
    Mutexdestroy(This._mutex2)
    Mutexdestroy(This._mutex3)
End Destructor

'---------------------------------------------------

Type ThreadPooling
    Public:
        Declare Constructor()
        Declare Sub PoolingSubmit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
        Declare Sub PoolingWait()
        Declare Sub PoolingWait(values() As String)
        
        Declare Property PoolingState() As Ubyte
        
        Declare Destructor()
    Private:
        Dim As Function(Byval p As Any Ptr) As String _pThread0
        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 _mutex1
        Dim As Any Ptr _mutex2
        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 Type

Constructor ThreadPooling()
    Redim This._pThread(0)
    Redim This._p(0)
    Redim This._returnF(0)
    This._mutex1 = Mutexcreate()
    This._mutex2 = Mutexcreate()
    Mutexlock(This._mutex2)
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Constructor

Sub ThreadPooling.PoolingSubmit(Byval pThread As Function(Byval As Any Ptr) As String, Byval p As Any Ptr = 0)
    Mutexlock(This._mutex1)
    If Ubound(This._pThread) = 0 Then
        Mutexunlock(This._mutex2)
    End If
    Redim Preserve This._pThread(Ubound(This._pThread) + 1)
    This._pThread(Ubound(This._pThread)) = pThread
    Redim Preserve This._p(Ubound(This._p) + 1)
    This._p(Ubound(This._p)) = p
    Mutexunlock(This._mutex1)
    This._state = 1
End Sub

Sub ThreadPooling.PoolingWait()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    This._end = 0
    Redim This._returnF(0)
    This._state = 0
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Sub

Sub ThreadPooling.PoolingWait(values() As String)
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    This._end = 0
    If Ubound(This._returnF) > 0 Then
        Redim values(1 To Ubound(This._returnF))
        For I As Integer = 1 To Ubound(This._returnF)
            values(I) = This._returnF(I)
        Next I
        Redim This._returnF(0)
    Else
        Erase values
    End If
    This._state = 0
    This._pt= Threadcreate(@ThreadPooling._Thread, @This)
End Sub

Property ThreadPooling.PoolingState() As Ubyte
    Return This._state
End Property

Sub ThreadPooling._Thread(Byval p As Any Ptr)
    Dim As ThreadPooling Ptr pThis = p
    Do
        Mutexlock(pThis->_mutex1)
        While Ubound(pThis->_pThread) = 0
            Mutexunlock(pThis->_mutex1)
            pThis->_state = 4
            Mutexlock(pThis->_mutex2)
            If pThis->_end = 1 And Ubound(pThis->_pThread) = 0 Then Exit Sub
            Mutexlock(pThis->_mutex1)
        Wend
        pThis->_pThread0 = pThis->_pThread(1)
        pThis->_p0 = pThis->_p(1)
        For I As Integer = 2 To Ubound(pThis->_pThread)
            pThis->_pThread(I - 1) = pThis->_pThread(I)
            pThis->_p(I - 1) = pThis->_p(I)
        Next I
        Redim Preserve pThis->_pThread(Ubound(pThis->_pThread) - 1)
        Redim Preserve pThis->_p(Ubound(pThis->_p) - 1)
        Mutexunlock(pThis->_mutex1)
        Redim Preserve pThis->_ReturnF(Ubound(pThis->_returnF) + 1)
        pThis->_state = 2
        pThis->_returnF(Ubound(pThis->_returnF)) = pThis->_pThread0(pThis->_p0)
    Loop
End Sub

Destructor ThreadPooling()
    This._end = 1
    Mutexunlock(This._mutex2)
    .ThreadWait(This._pt)
    Mutexdestroy(This._mutex1)
    Mutexdestroy(This._mutex2)
End Destructor

'---------------------------------------------------

Function UserCode (Byval p As Any Ptr) As String
    Dim As String Ptr ps = p
    For I As Integer = 1 To 2
        Print *ps;
        Sleep 100, 1
    Next I
    Return ""
End Function

Dim As String s(0 To 31)
For I As Integer = 0 To 15
    s(I) = Str(hex(I))
Next I
For I As Integer = 16 To 31
    s(I) = Chr(55 + I)
Next I

'---------------------------------------------------

Dim As ThreadInitThenMultiStart ta(0 To 31)
Dim As ThreadPooling tb(0 To 31)

'---------------------------------------------------

#macro ThreadInitThenMultiStartSequence(nbThread)
Scope
    Dim As Double t = Timer
    For I As Integer = 0 To 32 - nbThread Step nbThread
        For J As Integer = 0 To nbThread - 1
            ta(J).ThreadInit(@UserCode, @s(I + J))
            ta(J).ThreadStart()
        Next J
        For J As Integer = 0 To nbThread - 1
            ta(J).ThreadWait()
        Next J
    Next I
    t = Timer - t
    Print
    Print Using "#.## s"; t
End Scope
#endmacro

#macro ThreadPoolingSequence(nbThread)
Scope
    Dim As Double t = Timer
    For I As Integer = 0 To 32 - nbThread Step nbThread
        For J As Integer = 0 To nbThread - 1
            tb(J).PoolingSubmit(@UserCode, @s(I + J))
        Next J
    Next I
    For I As Integer = 0 To nbThread - 1
        tb(I).PoolingWait()
    Next I
    t = Timer - t
    Print
    Print Using "#.## s"; t
End Scope
#endmacro

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 1 secondary thread:"
Print "    ";
ThreadInitThenMultiStartSequence(1)
Print

'---------------------------------------------------

Print "'ThreadPooling' with 1 secondary thread:"
Print "    ";
ThreadPoolingSequence(1)
Print
Print

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 2 secondary threads:"
Print "    ";
ThreadInitThenMultiStartSequence(2)
Print

'---------------------------------------------------

Print "'ThreadPooling' with 2 secondary threads:"
Print "    ";
ThreadPoolingSequence(2)
Print
Print

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 4 secondary threads:"
Print "    ";
ThreadInitThenMultiStartSequence(4)
Print

'---------------------------------------------------

Print "'ThreadPooling' with 4 secondary threads:"
Print "    ";
ThreadPoolingSequence(4)
Print
Print

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 8 secondary threads:"
Print "    ";
ThreadInitThenMultiStartSequence(8)
Print

'---------------------------------------------------

Print "'ThreadPooling' with 8 secondary threads:"
Print "    ";
ThreadPoolingSequence(8)
Print
Print

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 16 secondary threads:"
Print "    ";
ThreadInitThenMultiStartSequence(16)
Print

'---------------------------------------------------

Print "'ThreadPooling' with 16 secondary threads:"
Print "    ";
ThreadPoolingSequence(16)
Print
Print

'---------------------------------------------------

Print "'ThreadInitThenMultiStart' with 32 secondary threads:"
Print "    ";
ThreadInitThenMultiStartSequence(32)
Print

'---------------------------------------------------

Print "'ThreadPooling' with 32 secondary threads:"
Print "    ";
ThreadPoolingSequence(32)
Print

Sleep
'ThreadInitThenMultiStart' with 1 secondary thread:
00112233445566778899AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVV
7.45 s

'ThreadPooling' with 1 secondary thread:
00112233445566778899AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVV
7.46 s


'ThreadInitThenMultiStart' with 2 secondary threads:
01012332545467768998ABABCDCDFEFEGHGHIJIJKLKLMNMNOPPOQRQRSTTSUVVU
3.73 s

'ThreadPooling' with 2 secondary threads:
01012323455467769898ABABCDDCEFFEHGHGIJJILKKLMNNMPOOPQRQRTSTSVUVU
3.73 s


'ThreadInitThenMultiStart' with 4 secondary threads:
021321304567456789ABAB98CDEFCDFEHJIGHGIJKLMNMNLKQROPROPQUVTSVSUT
1.87 s

'ThreadPooling' with 4 secondary threads:
01233210765445769BA88A9BDFECCFDEJGIHGIHJMKLNNMLKPOQROPQRSTVUVTUS
1.86 s


'ThreadInitThenMultiStart' with 8 secondary threads:
2571436003641572A98BCDEF9FDACB8EGHIJLKMNLKJMNHIGOPQRSTUVUTVRSQPO
0.93 s

'ThreadPooling' with 8 secondary threads:
102354677645230198BAEDCFFCEADB98HGJLMIKNNKIMLJGHVSPORTUQUQTOPRVS
0.93 s


'ThreadInitThenMultiStart' with 16 secondary threads:
2301457689ABCDEF2FDECBA986754103GHIKLJMNOQRPSTUVGHUVTSRPQONMJLKI
0.47 s

'ThreadPooling' with 16 secondary threads:
2014367589ABCEFD2EDFCAB958763410IHQPLSONMVTJKGRUIRUKGJTMVNOSLPHQ
0.47 s


'ThreadInitThenMultiStart' with 32 secondary threads:
01234U6789BACDFEGHIJKLMNOPQRST5VT5VSRQPNMLOKJIHGEFDCAB9876U42130
0.22 s

'ThreadPooling' with 32 secondary threads:
1203456789BCAEDFHGJKILMNOPQSRTUV1VRTUSQPONMLIKJGHFDEACB987654302
0.24 s
Note:
I think that these too good results are due to the fact that the user procedure spends almost all of its time waiting (see the 2 'Sleep 400,1'), which is very too favorable for measuring a real parallelism gain.
But that proves on the other hand the good use of the threads in the algorithm.
Post Reply