How to Manage a Critical Section of the code of a Thread in FB

Forum for discussion about the documentation project.
fxm
Moderator
Posts: 12455
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by fxm »

Time wasted when running a user task either by procedure calling method, by elementary threading method, or by various thread pooling methods
(post moved from Programming/General/wth -- Thread time .vs Subroutine time)

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:

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

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

#include once "crt/string.bi"
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 _mutex
        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
        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._mutex = MutexCreate()
    This._cond1 = CondCreate()
    This._cond2 = CondCreate()
    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._mutex)
    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
    CondSignal(This._cond2)
    This._state = 1
    MutexUnlock(This._mutex)
End Sub

Sub ThreadPooling.PoolingWait()
    MutexLock(This._mutex)
    While (This._state And 11) > 0
        CondWait(This._Cond1, This._mutex)
    Wend
    ReDim This._returnF(0)
    This._state = 0
    MutexUnlock(This._mutex)
End Sub

Sub ThreadPooling.PoolingWait(values() As String)
    MutexLock(This._mutex)
    While (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 UBound(This._returnF)
            values(I) = This._returnF(I)
        Next I
        ReDim This._returnF(0)
    Else
        Erase values
    End If
    This._state = 0
    MutexUnlock(This._mutex)
End Sub

Property ThreadPooling.PoolingState() As UByte
    If UBound(This._p) > 0 Then
        Return 8 + This._state
    Else
        Return This._state
    End If
End Property

Sub ThreadPooling._Thread(ByVal p As Any Ptr)
    Dim As ThreadPooling Ptr pThis = p
    Do
        MutexLock(pThis->_mutex)
        If UBound(pThis->_pThread) = 0 Then
            pThis->_state = 4
            CondSignal(pThis->_cond1)
            While UBound(pThis->_pThread) = 0
                CondWait(pThis->_cond2, pThis->_mutex)
                If pThis->_end = 1 Then Exit Sub
            Wend
        End If
        pThis->_pThread0 = pThis->_pThread(1)
        pThis->_p0 = pThis->_p(1)
        If UBound(pThis->_pThread) > 1 Then
            memmove(@pThis->_pThread(1), @pThis->_pThread(2), (UBound(pThis->_pThread) - 1) * SizeOf(pThis->_pThread))
            memmove(@pThis->_p(1), @pThis->_p(2), (UBound(pThis->_p) - 1) * SizeOf(pThis->_p))
        End If
        ReDim Preserve pThis->_pThread(UBound(pThis->_pThread) - 1)
        ReDim Preserve pThis->_p(UBound(pThis->_p) - 1)
        MutexUnlock(pThis->_mutex)
        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()
    MutexLock(This._mutex)
    This._end = 1
    CondSignal(This._cond2)
    MutexUnlock(This._mutex)
    .ThreadWait(This._pt)
    MutexDestroy(This._mutex)
    CondDestroy(This._cond1)
    CondDestroy(This._cond2)
End Destructor

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

Type ThreadDispatching
    Public:
        Declare Constructor(ByVal nbMaxSecondaryThread As Integer = 1, ByVal nbMinSecondaryThread As Integer = 0)
        Declare Sub DispatchingSubmit(ByVal pThread As Function(ByVal As Any Ptr) As String, ByVal p As Any Ptr = 0)
        Declare Sub DispatchingWait()
        Declare Sub DispatchingWait(values() As String)

        Declare Property DispatchingThread() As Integer
        Declare Sub DispatchingState(state() As Ubyte)

        Declare Destructor()
    Private:
        Dim As Integer _nbmst
        Dim As Integer _dstnb
        Dim As ThreadPooling Ptr _tp(Any)
End Type

Constructor ThreadDispatching(ByVal nbMaxSecondaryThread As Integer = 1, ByVal nbMinSecondaryThread As Integer = 0)
    This._nbmst = nbMaxSecondaryThread
    If nbMinSecondaryThread > nbMaxSecondaryThread Then
        nbMinSecondaryThread = nbMaxSecondaryThread
    End If
    If nbMinSecondaryThread > 0 Then
        ReDim This._tp(nbMinSecondaryThread - 1)
        For I As Integer = 0 To nbMinSecondaryThread - 1
            This._tp(I) = New ThreadPooling
        Next I
    End If
End Constructor

Sub ThreadDispatching.DispatchingSubmit(ByVal 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)->PoolingSubmit(pThread, p)
            Exit Sub
        End If
    Next I
    If UBound(This._tp) < This._nbmst - 1 Then
        ReDim Preserve This._tp(UBound(This._tp) + 1)
        This._tp(UBound(This._tp)) = New ThreadPooling
        This._tp(UBound(This._tp))->PoolingSubmit(pThread, p)
    ElseIf UBound(This._tp) >= 0 Then
        This._tp(This._dstnb)->PoolingSubmit(pThread, p)
        This._dstnb = (This._dstnb + 1) Mod This._nbmst
    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 String)
    Dim As String s()
    For I As Integer = 0 To UBound(This._tp)
        This._tp(I)->PoolingWait(s())
        If UBound(s) >= 1 Then
            If UBound(values) = -1 Then
                ReDim Preserve values(1 To UBound(values) + UBound(s) + 1)
            Else
                ReDim Preserve values(1 To UBound(values) + UBound(s))
            End If
            For I As Integer = 1 To UBound(s)
                values(UBound(values) - UBound(s) + I) = s(I)
            Next I
        End If
    Next I
End Sub

Property ThreadDispatching.DispatchingThread() As Integer
    Return UBound(This._tp) + 1
End Property

Sub ThreadDispatching.DispatchingState(state() As Ubyte)
    If UBound(This._tp) >= 0 Then
        Redim state(1 To UBound(This._tp) + 1)
        For I As Integer = 0 To UBound(This._tp)
            state(I + 1) = This._tp(I)->PoolingState
        Next I
    End If
End Sub

Destructor ThreadDispatching()
    For I As Integer = 0 To UBound(This._tp)
        Delete This._tp(I)
    Next I
End Destructor

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

Sub s(Byval p As Any Ptr)
    '' user task
End Sub

Function f(Byval p As Any Ptr) As String
    '' user task
    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 procedure calling method,"
Print "   or by various threading methods."
Print

Scope
    Dim As Double t = Timer
    For I As Integer = 1 To 1000000
        s(0)
    Next I
    t = Timer - t
    Print Using "      - Using procedure calling method        : ###.###### ms"; t / 1000
    Print
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 "      - Using elementary threading method     : ###.###### ms"; t
    Print
End Scope

Scope
    Dim As ThreadInitThenMultiStart ts
    Dim As Double t = Timer
    ts.ThreadInit(@f)
    For I As Integer = 1 To 10000
        ts.ThreadStart()
        ts.ThreadWait()
    Next I
    t = Timer - t
    Print Using "      - Using ThreadInitThenMultiStart method : ###.###### ms"; t / 10
End Scope

Scope
    Dim As ThreadPooling tp
    Dim As Double t = Timer
    For I As Integer = 1 To 10000
        tp.PoolingSubmit(@f)
    Next I
    tp.PoolingWait()
    t = Timer - t
    Print Using "      - Using ThreadPooling method            : ###.###### ms"; t / 10
End Scope

Scope
    Dim As ThreadDispatching td
    Dim As Double t = Timer
    For I As Integer = 1 To 10000
        td.DispatchingSubmit(@f)
    Next I
    td.DispatchingWait()
    t = Timer - t
    Print Using "      - Using ThreadDispatching method        : ###.###### ms"; t / 10
End Scope

Print
Sleep

Code: Select all

Mean time wasted when running a user task :
   either by procedure calling method,
   or by various threading methods.

      - Using procedure calling method        :   0.000033 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 results with my PC show that a thread pooling method allows to gain about 140 µs by user task compared to a elementary threading method, but it remains about 7 µs to compare to 0.03 µs for a simple calling method.


[edit]
If this code does not compile (line 192) with fbc version 1.20 for example, see the bug post: https://www.freebasic.net/forum/viewtop ... 41#p305541
Thus see the below post: https://www.freebasic.net/forum/viewtop ... 91#p305891
The bug in current fbc 1.20 version can be fixed by following workaround:

Code: Select all

'        ReDim Preserve pThis->_pThread(UBound(pThis->_pThread) - 1)  '' bug in current fbc version 1.20
        With *pThis                                                   '' workaround for the bug
            ReDim Preserve ._pThread(UBound(pThis->_pThread) - 1)     '' workaround for the bug
        End With                                                      '' workaround for the bug
Last edited by fxm on Jan 14, 2025 12:56, edited 9 times in total.
Reason: Potentiel compile bug with fbc version 1.20 for example.
fxm
Moderator
Posts: 12455
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by fxm »

What is the synchronization latency when synchronizing threads either by mutual exclusions or by conditional variables?

The synchronization waiting phase of each thread should not consume any CPU resources like 'Sleep', which is the case of 'MutexLock' and 'CondWait' instructions.
Thread synchronization by mutual exclusions or by conditional variables adds latency to the initial execution time of the threads, but this latency (a few microseconds) is infinitely shorter than that of a simple simple wait loop (a few milliseconds at best) containing the shortest sleep ('Sleep 1, 1') with a flag test.

The following code allows to estimate this synchronization latency between the main thread and a child thread, by using either simple flags, either mutual exclusions, or conditional variables:

Code: Select all

Dim Shared As Any Ptr mutex0, mutex1, mutex2, mutex, cond1, cond2, pt
Dim Shared As Integer flag1, flag2
Dim As Double t

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

#if defined(__FB_WIN32__)
Declare Function _setTimer Lib "winmm" Alias "timeBeginPeriod"(ByVal As Ulong = 1) As Long
Declare Function _resetTimer Lib "winmm" Alias "timeEndPeriod"(ByVal As Ulong = 1) As Long
#endif

Sub ThreadFlag(ByVal p As Any Ptr)
    Mutexunlock(mutex0)  '' unlock mutex for main thread
    For I As Integer = 1 To 100
        While flag1 = 0
            Sleep 1, 1
        Wend
        flag1 = 0
        ' only child thread code runs (location for example)
        flag2 = 1
    Next I
End Sub

mutex0 = Mutexcreate()
Mutexlock(mutex0)

pt = ThreadCreate(@ThreadFlag)
Mutexlock(mutex0)  '' wait for thread launch (mutex unlock from child thread)
Print "Thread synchronization latency by simple flags:"
#if defined(__FB_WIN32__)
    _setTimer()
    Print "(in high resolution OS cycle period)"
#else
    Print "(in normal resolution OS cycle period)"
#endif
t = Timer
For I As Integer = 1 To 100
    flag1 = 1
    While flag2 = 0
        sleep 1, 1
    Wend
    flag2 = 0
    ' only main thread code runs (location for example)
Next I
t = Timer - t
#if defined(__FB_WIN32__)
    _resetTimer()
#endif
Threadwait(pt)
Print Using "####.## milliseconds per double synchronization (round trip)"; t * 10
Print

Mutexdestroy(mutex0)

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

Sub ThreadMutex(Byval p As Any Ptr)
    Mutexunlock(mutex0)  '' unlock mutex for main thread
    For I As Integer = 1 to 100000
        Mutexlock(mutex1)    '' wait for mutex unlock from main thread
        ' only child thread code runs
        Mutexunlock(mutex2)  '' unlock mutex for main thread
    Next I
End Sub

mutex0 = Mutexcreate()
mutex1 = Mutexcreate()
mutex2 = Mutexcreate()
Mutexlock(mutex0)
Mutexlock(mutex1)
Mutexlock(mutex2)

pt = ThreadCreate(@ThreadMutex)
Mutexlock(mutex0)  '' wait for thread launch (mutex unlock from child thread)
Print "Thread synchronization latency by mutual exclusions:"
t = Timer
For I As Integer = 1 To 100000
    Mutexunlock(mutex1)  '' mutex unlock for child thread
    Mutexlock(mutex2)    '' wait for mutex unlock from child thread
    ' only main thread code runs
Next I
t = Timer - t
Threadwait(pt)
Print Using "####.## microseconds per double synchronization (round trip)"; t * 10
Print

Mutexdestroy(mutex0)
Mutexdestroy(mutex1)
Mutexdestroy(mutex2)

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

Sub ThreadCondVar(ByVal p As Any Ptr)
    Mutexunlock(mutex0)  '' unlock mutex for main thread
    For I As Integer = 1 To 100000
        MutexLock(mutex)
        While flag1 = 0
            CondWait(cond1, mutex)  '' wait for conditional signal from main thread
        Wend
        flag1 = 0
        ' only child thread code runs (location for example)
        flag2 = 1
        CondSignal(cond2)  '' send conditional signal to main thread
        MutexUnlock(mutex)
    Next I
End Sub

mutex0 = Mutexcreate()
mutex = Mutexcreate()
Mutexlock(mutex0)
cond1 = Condcreate()
cond2 = Condcreate()

pt = ThreadCreate(@ThreadCondVar)
Mutexlock(mutex0)  '' wait for thread launch (mutex unlock from child thread)
Print "Thread synchronization latency by conditional variables:"
t = Timer
For I As Integer = 1 To 100000
    MutexLock(mutex)
    flag1 = 1
    CondSignal(cond1)  '' send conditional signal to main thread
    While flag2 = 0
        CondWait(Cond2, mutex)  '' wait for conditional signal from child thread
    Wend
    flag2 = 0
    ' only child thread code runs (location for example)
    MutexUnlock(mutex)
Next I
t = Timer - t
Threadwait(pt)
Print Using "####.## microseconds per double synchronization (round trip)"; t * 10
Print

Mutexdestroy(mutex0)
Mutexdestroy(mutex)
Conddestroy(cond1)
Conddestroy(cond2)

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

Sleep
  • Example of results:

    Code: Select all

    Thread synchronization latency by simple flags:
    (in high resolution OS cycle period)
       2.02 milliseconds per double synchronization (round trip)
    
    Thread synchronization latency by mutual exclusions:
       5.93 microseconds per double synchronization (round trip)
    
    Thread synchronization latency by conditional variables:
       7.54 microseconds per double synchronization (round trip)
    

Example of thread synchronization for executing in concurrent or exclusive mode by using conditional variables:

Code: Select all

Dim Shared As Any Ptr pt, mutex1, mutex2, cond1, cond2
Dim Shared As Integer quit, flag1, flag2

Print "'1': Main thread procedure running (alone)"
Print "'2': Child thread procedure running (alone)"
Print "'-': Main thread procedure running (with the one of child thread)"
Print "'=': Child thread procedure running (with the one of main thread)"
Print

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

Sub ThreadCondCond(Byval p As Any Ptr)
    Do
        Mutexlock(mutex1)
        While flag1 = 0              '' test flag set from main thread
            CondWait(cond1, mutex1)  '' wait for conditional signal from main thread
        Wend
        flag1 = 0                    '' reset flag
        Mutexunlock(mutex1)
        If quit = 1 Then Exit Sub    '' exit the threading loop
        Prnt("=", 10)
        Mutexlock(mutex2)
        flag2 = 1                    '' set flag to main thread
        CondSignal(cond2)            '' send conditional signal to main thread
        Prnt("2", 10)
        Mutexunlock(mutex2)
    Loop
End Sub

mutex1 = Mutexcreate()
mutex2 = Mutexcreate()
cond1 = Condcreate()
cond2 = Condcreate()

pt = ThreadCreate(@ThreadCondCond)
For I As Integer = 1 To 10
    Mutexlock(mutex1)
    flag1 = 1                    '' set flag to child thread
    CondSignal(cond1)            '' send conditional signal to child thread
    Mutexunlock(mutex1)
    Prnt("-", 10)
    Mutexlock(mutex2)
    While flag2 = 0              '' test flag set from child thread
        CondWait(Cond2, mutex2)  '' wait for conditional signal from child thread
    Wend
    flag2 = 0                    '' reset flag
    Prnt("1", 10)
    Mutexunlock(mutex2)
Next I

MutexLock(mutex1)
quit = 1                         '' set quit for child thread
flag1 = 1
CondSignal(cond1)                '' send conditional signal to child thread
Mutexunlock(mutex1)
Threadwait(pt)                   '' wait for child thread to end
Print

Mutexdestroy(mutex1)
Mutexdestroy(mutex2)
Conddestroy(cond1)
Conddestroy(cond2)

Sleep
  • Output:

    Code: Select all

    '1': Main thread procedure running (alone)
    '2': Child thread procedure running (alone)
    '-': Main thread procedure running (with the one of child thread)
    '=': Child thread procedure running (with the one of main thread)
    
    -==-=-=--==--==-=-=-22222222221111111111-=-=-=-==--==-=--==-22222222221111111111-=-=-==-=-=--=-=-==-22222222221111111111-=-=-=-=-=-=--==-=-=22222222221111111111-==--==--==-=-=--==-22222222221111111111
    
PeterHu
Posts: 218
Joined: Jul 24, 2022 4:57

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by PeterHu »

fxm wrote: Mar 03, 2023 16:10

Code: Select all


        ReDim Preserve pThis->_pThread(UBound(pThis->_pThread) - 1) ' line 192
        ReDim Preserve pThis->_p(UBound(pThis->_p) - 1)
        MutexUnlock(pThis->_mutex)
        ReDim Preserve pThis->_ReturnF(UBound(pThis->_returnF) + 1)
        


[edit]
If this code does not compile (line 192) with fbc version 1.20 for example, see the bug post: https://www.freebasic.net/forum/viewtop ... 41#p305541
So may I ask how to get this work with fbc1.20 please?
fxm
Moderator
Posts: 12455
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by fxm »

For example, replace:

Code: Select all

        ReDim Preserve pThis->_pThread(UBound(pThis->_pThread) - 1)
with:

Code: Select all

        With *pThis
            ReDim Preserve ._pThread(UBound(pThis->_pThread) - 1)
        End with
PeterHu
Posts: 218
Joined: Jul 24, 2022 4:57

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by PeterHu »

fxm wrote: Dec 28, 2024 15:16 For example, replace:

Code: Select all

        ReDim Preserve pThis->_pThread(UBound(pThis->_pThread) - 1)
with:

Code: Select all

        With *pThis
            ReDim Preserve ._pThread(UBound(pThis->_pThread) - 1)
        End with
Thank you,now the example works with fbc1.20.
But,why below code in line 129 doesn't have to change to

Code: Select all

with *pThis
which isn't the same situation?

Code: Select all

sub ThreadPooling.PoolingSubmit(byval pThread as function(byval as any ptr) as string, byval p as any ptr = 0)
    mutexlock(this._mutex)
    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
    condsignal(this._cond2)
    this._state = 1
    mutexunlock(this._mutex)
end sub
fxm
Moderator
Posts: 12455
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by fxm »

See https://www.freebasic.net/forum/viewtop ... 44#p305544,

and next: https://www.freebasic.net/forum/viewtop ... 46#p305546:
coderJeff wrote: Nov 17, 2024 17:50 .....
The parser doesn't know how much expression to parse before it has found the final name of the array. I think in the case of the procedure pointer, there must be checks missing telling the parser either what to expect next or what to accept, or both. At the moment I don't fully know the exact problem with procedure pointers.
PeterHu
Posts: 218
Joined: Jul 24, 2022 4:57

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by PeterHu »

fxm wrote: Dec 29, 2024 5:31 See https://www.freebasic.net/forum/viewtop ... 44#p305544,

and next: https://www.freebasic.net/forum/viewtop ... 46#p305546:
coderJeff wrote: Nov 17, 2024 17:50 .....
The parser doesn't know how much expression to parse before it has found the final name of the array. I think in the case of the procedure pointer, there must be checks missing telling the parser either what to expect next or what to accept, or both. At the moment I don't fully know the exact problem with procedure pointers.
Got it.Thank you!
fxm
Moderator
Posts: 12455
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by fxm »

What happens when multiple threads are waiting on the same condition variable?

If 'CondSignal()' is used:
  • The specification states that the thread scheduler arbitrarily notifies one of the threads waiting on the condition variable.
    Since this method notifies an arbitrary thread, some documentation also states that it is therefore possible for a waiting thread to never be notified if it is never alone in the queue on the condition variable.
    But my tests on Windows seem to show that the order of entry into the queue on a same condition variable determines the order of exit: first in, first out.
    I have not tested this on Linux or any other platform, but in any case this behavior I observed is unspecified.
If 'CondBroadcast()' is used:
  • The specification states that the thread scheduler notifies all threads waiting on the condition variable in an arbitrary order.
Example:
  • The example below works with 6 threads (in addition to the main thread).
    The first 3 threads (#1 to #3) are waiting on their own condition variable, while the last 3 threads (#4 to #6) are waiting on a same other condition variable.
    These last 3 threads are awakened either on 'CondSignal()' or on 'CondBroadcast()'.

    Code: Select all

    Type ThreadData
        Dim As Integer id
        Dim As Any Ptr mutex
        Dim As Any Ptr cond
        Dim As Boolean flag
        Dim As Boolean quit
        Dim As Any Ptr handle
        Declare Static Sub Thread(Byval p As Any Ptr)
    End Type
    
    Sub ThreadData.Thread(Byval p As Any Ptr)
        Dim As ThreadData Ptr pdata = p
        Print "   thread #" & pdata->id & " is running"
        Do
            Mutexlock(pdata->mutex)
                While pdata->flag = False
                    Condwait(pdata->cond, pdata->mutex)
                Wend
                pdata->flag = False
            Mutexunlock(pdata->mutex)
            If pdata->quit = False Then
                Print "   thread #" & pdata->id & " is signaled"
            Else
                Exit Do
            End If
        Loop
        Print "   thread #" & pdata->id & " is finishing"
    End Sub
    
    
    Dim As Any Ptr mutex = Mutexcreate()
    Dim As Any Ptr cond(0 to 3) = {Condcreate(), Condcreate(), Condcreate(), Condcreate()}
    Dim As ThreadData mythreads(1 To 6) = {Type(1, mutex, cond(1)), Type(2, mutex, cond(2)), Type(3, mutex, cond(3)), _
                                           Type(4, mutex, cond(0)), Type(5, mutex, cond(0)), Type(6, mutex, cond(0))}
    
    Print "Threads from #1 to #6 are created:"
    For I As Integer = Lbound(mythreads) To Ubound(mythreads)
        mythreads(I).handle = Threadcreate(@ThreadData.Thread, @mythreads(I))
    Next I
    Sleep 1000, 1  '' wait for all threads started
    Print
    Print "----------------------------------------------------------"
    Print
    
    For I As Integer = 3 To 1 Step -1
        Print "Send a CondSignal to thread #" & I &":"
        Mutexlock(mutex)
            mythreads(I).flag = True
            Condsignal(cond(I))
        Mutexunlock(mutex)
        Sleep 1000, 1  '' wait for the thread loop completed
        Print
    Next I
    Print "----------------------------------------------------------"
    Print
    
    Print "Send a single CondBroadcast to all threads from #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condbroadcast(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for all thread loops completed
    Print "Send a single CondBroadcast to all threads from #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condbroadcast(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for all thread loops completed
    Print "Send a single CondBroadcast to all threads from #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condbroadcast(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for all thread loops completed
    Print
    Print "----------------------------------------------------------"
    Print
    
    Print "Send a CondSignal to any thread among #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condsignal(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for a thread loop completed
    Print "Send a CondSignal to any thread among #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condsignal(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for a thread loop completed
    Print "Send a CondSignal to any thread among #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condsignal(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for a thread loop completed
    Print
    
    Print "Send a CondSignal to any thread among #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condsignal(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for a thread loop completed
    Print "Send a CondSignal to any thread among #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condsignal(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for a thread loop completed
    Print "Send a CondSignal to any thread among #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condsignal(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for a thread loop completed
    Print
    
    Print "Send a CondSignal to any thread among #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condsignal(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for a thread loop completed
    Print "Send a CondSignal to any thread among #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condsignal(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for a thread loop completed
    Print "Send a CondSignal to any thread among #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
        Next I
        Condsignal(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for a thread loop completed
    Print
    Print "----------------------------------------------------------"
    Print
    
    For I As Integer = 1 To 3
        Print "Send to finish a CondSignal to thread #" & I &":"
        Mutexlock(mutex)
            mythreads(I).flag = True
            mythreads(I).quit = True
            Condsignal(cond(I))
        Mutexunlock(mutex)
        Sleep 1000, 1  '' wait for the thread loop completed
        Print
    Next I
    Print "----------------------------------------------------------"
    Print
    
    Print "Send to finish a single CondBroadcast to all threads from #4 to #6:"
    Mutexlock(mutex)
        For I As Integer = 4 To 6
            mythreads(I).flag = True
            mythreads(I).quit = True
        Next I
        Condbroadcast(cond(0))
    Mutexunlock(mutex)
    Sleep 1000, 1  '' wait for all thread loops completed
    Print
    Print "----------------------------------------------------------"
    Print
    
    For I As Integer = 1 To 3
        Threadwait(mythreads(I).handle)
        Conddestroy(cond(I))
    Next I
    For I As Integer = 4 To 6
        Threadwait(mythreads(I).handle)
    Next I
    Print "All threads from #1 to #6 are finished."
    Print
    
    Mutexdestroy(mutex)
    Conddestroy(cond(0))
    
    Sleep
    
    • Output example:

      Code: Select all

      Threads from #1 to #6 are created:
         thread #1 is running
         thread #3 is running
         thread #2 is running
         thread #5 is running
         thread #4 is running
         thread #6 is running
      
      ----------------------------------------------------------
      
      Send a CondSignal to thread #3:
         thread #3 is signaled
      
      Send a CondSignal to thread #2:
         thread #2 is signaled
      
      Send a CondSignal to thread #1:
         thread #1 is signaled
      
      ----------------------------------------------------------
      
      Send a single CondBroadcast to all threads from #4 to #6:
         thread #5 is signaled
         thread #6 is signaled
         thread #4 is signaled
      Send a single CondBroadcast to all threads from #4 to #6:
         thread #6 is signaled
         thread #4 is signaled
         thread #5 is signaled
      Send a single CondBroadcast to all threads from #4 to #6:
         thread #5 is signaled
         thread #4 is signaled
         thread #6 is signaled
      
      ----------------------------------------------------------
      
      Send a CondSignal to any thread among #4 to #6:
         thread #5 is signaled
      Send a CondSignal to any thread among #4 to #6:
         thread #4 is signaled
      Send a CondSignal to any thread among #4 to #6:
         thread #6 is signaled
      
      Send a CondSignal to any thread among #4 to #6:
         thread #5 is signaled
      Send a CondSignal to any thread among #4 to #6:
         thread #4 is signaled
      Send a CondSignal to any thread among #4 to #6:
         thread #6 is signaled
      
      Send a CondSignal to any thread among #4 to #6:
         thread #5 is signaled
      Send a CondSignal to any thread among #4 to #6:
         thread #4 is signaled
      Send a CondSignal to any thread among #4 to #6:
         thread #6 is signaled
      
      ----------------------------------------------------------
      
      Send to finish a CondSignal to thread #1:
         thread #1 is finishing
      
      Send to finish a CondSignal to thread #2:
         thread #2 is finishing
      
      Send to finish a CondSignal to thread #3:
         thread #3 is finishing
      
      ----------------------------------------------------------
      
      Send to finish a single CondBroadcast to all threads from #4 to #6:
         thread #4 is finishing
         thread #5 is finishing
         thread #6 is finishing
      
      ----------------------------------------------------------
      
      All threads from #1 to #6 are finished.
      
fxm
Moderator
Posts: 12455
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by fxm »

How to optimize sequencing of successive user tasks executed by threading?

The delay between the return of 'ThreadCreate()' and the start of the thread code (first line of thread code) can be estimated at about 50 microseconds on average, but can go up to a few milliseconds at worst.
(see FAQ: What is the execution delay of the code of a thread after the thread is created by 'ThreadCreate'?).

This is why a child thread can be launched only once (by a constructor for example) and execute a permanent waiting loop of user tasks (to avoid a thread launch latency each time), then at end stopped (by a destructor).
The synchronization between the main thread and the child thread (start of each user task and user task completed) can be managed by means of 2 mutexes.

Example for managing console input/output by multi-threading (background user tasks) without restarting a thread each time:

Code: Select all

Type thread
    Public:
        Dim As String s                   '' user text
        Dim As Sub(Byref As thread) task  '' pointer to user task
        Declare Sub Launch()              '' launch user task
        Declare Sub Wait()                '' wait for user task completed
        Declare Constructor()
        Declare Destructor()
    Private:
        Dim As Any Ptr mutex1
        Dim As Any Ptr mutex2
        Dim As Any Ptr handle
        Dim As Boolean quit
        Declare Static Sub proc(Byval pthread as thread Ptr)
End Type

Constructor thread()
    This.mutex1 = MutexCreate
    This.mutex2 = MutexCreate
    Mutexlock(This.mutex1)
    Mutexlock(This.mutex2)
    This.handle = ThreadCreate(Cptr(Any Ptr, @thread.proc), @This)
End Constructor

Destructor thread()
    This.quit = True
    Mutexunlock(This.mutex1)
    ThreadWait(This.handle)
    Mutexdestroy(This.mutex1)
    Mutexdestroy(This.mutex2)
End Destructor

Sub thread.proc(Byval pthread as thread Ptr)
    Do
        MutexLock(pthread->mutex1)    '' wait for lunching task
        If pthread->quit = True Then Exit Sub
        pthread->task(*pthread)
        Mutexunlock(pthread->mutex2)  '' task completed
    Loop
End Sub

Sub thread.Launch()
    Mutexunlock(This.mutex1)
End Sub

Sub thread.Wait()
    Mutexlock(This.mutex2)
End Sub

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

Sub userTask1(Byref t As thread)   '' task1 to execute by the thread
    Print t.s
End Sub

Sub userTask2(Byref t As thread)   '' task2 to execute by the thread
    Line Input "? ", t.s
End Sub

Dim As thread myThread

myThread.task = @userTask1
myThread.s = "How old are you?"
myThread.Launch()
myThread.Wait()
'.....

myThread.task = @userTask2
myThread.Launch()
myThread.Wait()
'.....

myThread.task = @userTask1
myThread.s = "You are " & myThread.s & " old."
myThread.Launch()
myThread.Wait()
'.....

myThread.s = ""
myThread.Launch()
myThread.Wait()
'.....

myThread.s = "Do you want the date or time (D/T)?"
myThread.Launch()
myThread.Wait()
'.....

myThread.task = @userTask2
myThread.Launch()
myThread.Wait()
'.....

myThread.task = @userTask1
If Ucase(myThread.s) = "D" Then myThread.s = Date
If Ucase(myThread.s) = "T" Then myThread.s = Time
myThread.Launch()
myThread.Wait()
'.....

Print
Print "Completed."
Sleep
  • Output example:

    Code: Select all

    How old are you?
    ? 74
    You are 74 old.
    
    Do you want the date or time (D/T)?
    ? t
    16:16:25
    
    Completed.
    
fxm
Moderator
Posts: 12455
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by fxm »

Why is multi-threaded performance heavily penalized by many shared memory write accesses?

Each core has its own cache memory that allows to buffer the useful data (in read and write) of the shared memory.
Consequently, in case of writing in the cache, a cache coherence algorithm between cores is executed if necessary to keep, for the common memory areas between caches, the most recent values ​​among all the caches concerned.
It is this algorithm that heavily penalizes multi-threading performance in the case of multiple write accesses in shared memory.

It is therefore necessary to limit as much as possible the access of threads to shared memory, especially in writing.
For example, all intermediate results of threads could be performed in local memory, and only the final useful ones put in shared memory.
Note: But for all the pseudo-objects like var-len strings and dynamic arrays, only the descriptors can be put in local memory but not the data itself which is always on the heap (only fixed length data can be put in local memory).

Example of a member thread procedures computing the sum of the first N integers, by accumulation directly in the shared memory ('SumUpTo_1()') or internally in its local memory before copy back ('SumUpTo_2'()'):

Code: Select all

Type Thread
    Dim As Uinteger valueIN
    Dim As Double valueOUT
    Dim As Any Ptr pHandle
    Declare Static Sub SumUpTo_1(Byval pt As Thread Ptr)
    Declare Static Sub SumUpTo_2(Byval pt As Thread Ptr)
End type

Sub Thread.SumUpTo_1(Byval pt As Thread Ptr)
    pt->valueOut = 0
    For I As Uinteger = 1 To pt->valueIN
        pt->valueOUT += I
    Next I
End Sub

Sub Thread.SumUpTo_2(Byval pt As Thread Ptr)
    Dim As Double value = 0
    For I As Uinteger = 1 To pt->valueIN
        value += I
    Next I
    pt->valueOUT = value
End Sub

Sub MyThreads(Byval pThread As Any Ptr, Byval threadNB As Uinteger = 1)
    Dim As Thread td(1 To threadNB)
    Dim As Double t
    
    t = Timer
    For i As Integer = 1 To threadNB
        td(i).valueIN = 100000000 + i
        td(i).pHandle = Threadcreate(pThread, @td(i))
    Next I
    For i As Integer = 1 To threadNB
        ThreadWait(td(i).pHandle)
    Next I
    t = Timer - t

    For i As Integer = 1 To threadNB
        Print "   SumUpTo(" & td(i).valueIN & ") = " & td(i).valueOUT, "(right result : " & (100000000# + i) * (100000000# + i + 1) / 2 & ")"
    Next I
    Print "      total time : " & t & " s"
    Print
End Sub

Print
For i As Integer = 1 To 4
    Print "Each thread (in parallel) accumulating result directly in shared memory:"
    Mythreads(@Thread.SumUpTo_1, I)
    Print "Each thread (in parallel) accumulating result internally in its local memory:"
    Mythreads(@Thread.SumUpTo_2, I)
    Print "-----------------------------------------------------------------------------"
    Print
Next i

Sleep
  • Output example:

    Code: Select all

    Each thread (in parallel) accumulating result directly in shared memory:
       SumUpTo(100000001) = 5000000150000001  (right result : 5000000150000001)
          total time : 1.668927300015184 s
    
    Each thread (in parallel) accumulating result internally in its local memory:
       SumUpTo(100000001) = 5000000150000001  (right result : 5000000150000001)
          total time : 1.004467599958389 s
    
    -----------------------------------------------------------------------------
    
    Each thread (in parallel) accumulating result directly in shared memory:
       SumUpTo(100000001) = 5000000150000001  (right result : 5000000150000001)
       SumUpTo(100000002) = 5000000250000003  (right result : 5000000250000003)
          total time : 4.314032700025791 s
    
    Each thread (in parallel) accumulating result internally in its local memory:
       SumUpTo(100000001) = 5000000150000001  (right result : 5000000150000001)
       SumUpTo(100000002) = 5000000250000003  (right result : 5000000250000003)
          total time : 1.032165899962706 s
    
    -----------------------------------------------------------------------------
    
    Each thread (in parallel) accumulating result directly in shared memory:
       SumUpTo(100000001) = 5000000150000001  (right result : 5000000150000001)
       SumUpTo(100000002) = 5000000250000003  (right result : 5000000250000003)
       SumUpTo(100000003) = 5000000350000006  (right result : 5000000350000006)
          total time : 6.727616399944395 s
    
    Each thread (in parallel) accumulating result internally in its local memory:
       SumUpTo(100000001) = 5000000150000001  (right result : 5000000150000001)
       SumUpTo(100000002) = 5000000250000003  (right result : 5000000250000003)
       SumUpTo(100000003) = 5000000350000006  (right result : 5000000350000006)
          total time : 1.128656100041894 s
    
    -----------------------------------------------------------------------------
    
    Each thread (in parallel) accumulating result directly in shared memory:
       SumUpTo(100000001) = 5000000150000001  (right result : 5000000150000001)
       SumUpTo(100000002) = 5000000250000003  (right result : 5000000250000003)
       SumUpTo(100000003) = 5000000350000006  (right result : 5000000350000006)
       SumUpTo(100000004) = 5000000450000010  (right result : 5000000450000010)
          total time : 6.829728199980309 s
    
    Each thread (in parallel) accumulating result internally in its local memory:
       SumUpTo(100000001) = 5000000150000001  (right result : 5000000150000001)
       SumUpTo(100000002) = 5000000250000003  (right result : 5000000250000003)
       SumUpTo(100000003) = 5000000350000006  (right result : 5000000350000006)
       SumUpTo(100000004) = 5000000450000010  (right result : 5000000450000010)
          total time : 1.164915200012842 s
    
    -----------------------------------------------------------------------------
    
    One can check that the multi-threading performance is strongly penalized by many shared memory accesses in write mode:
    For the case where the thread accumulates the result in shared memory, there is no longer any gain from multi-threading (and even a little loss), whereas for the case where the thread accumulates the result in internal memory, the gain is almost at the theoretical maximum value.
On the other hand, we nevertheless observe a small degradation in multi-threading performance when accessing shared memory in read-only mode (compared to accessing local memory only).
badidea
Posts: 2626
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by badidea »

I opened a can of worms, it seems like.
But a known issue, see e.g. https://www.codeproject.com/Articles/85 ... ng-Threads or https://goog-perftools.sourceforge.net/ ... alloc.html
fxm
Moderator
Posts: 12455
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage a Critical Section of the code of a Thread in FB

Post by fxm »

On the other hand, I specify and confirm that there is only a small degradation in multi-threading performance when accessing shared memory only in read mode (compared to accessing local memory).
This problem is not specific to the heap but to any shared memory (for example even for the stack memory of a procedure creating the threads, like my example above).

A small example that tests the following 3 cases:
- Each thread (in parallel) accessing only local memory.
- Each thread (in parallel) accessing the shared memory in read mode only.
- Each thread (in parallel) accessing the shared memory in write mode only.

Code: Select all

Type Thread
    Dim As Uinteger valueS
    Dim As Any Ptr pHandle
    Declare Static Sub Proc0(Byval pt As Thread Ptr)
    Declare Static Sub Proc1(Byval pt As Thread Ptr)
    Declare Static Sub Proc2(Byval pt As Thread Ptr)
End type

Sub Thread.Proc0(Byval pt As Thread Ptr)
    Dim As Uinteger valueL
    Dim As Uinteger valueL0
    For I As Integer = 1 To 400000000
        valueL = valueL0 + valueL + I
    Next I
End Sub

Sub Thread.Proc1(Byval pt As Thread Ptr)
    Dim As Uinteger valueL
    For I As Integer = 1 To 400000000
        valueL = pt->valueS + I
    Next I
End Sub

Sub Thread.Proc2(Byval pt As Thread Ptr)
    Dim As Uinteger valueL
    For I As Integer = 1 To 400000000
        pt->valueS = valueL + I
    Next I
End Sub

Sub MyThreads(Byval pThread As Any Ptr, Byval threadNB As Uinteger = 1)
    Dim As Thread td(1 To threadNB)
    Dim As Double t
    
    t = Timer
    For i As Integer = 1 To threadNB
        td(i).pHandle = Threadcreate(pThread, @td(i))
    Next I
    For i As Integer = 1 To threadNB
        ThreadWait(td(i).pHandle)
    Next I
    t = Timer - t

    Print "   " & threadNB & " thread(s):"
    Print "      total time : " & t & " s"
    Print
End Sub

Print
For i As Integer = 1 To 6
    Print "Each thread (in parallel) accessing only local memory:"
    Mythreads(@Thread.Proc0, I)
    Print "Each thread (in parallel) accessing the shared memory in read mode only:"
    Mythreads(@Thread.Proc1, I)
    Print "Each thread (in parallel) accessing the shared memory in write mode only:"
    Mythreads(@Thread.Proc2, I)
    Print "-------------------------------------------------------------------------"
    Print
Next i

Sleep
  • Output example:

    Code: Select all

    Each thread (in parallel) accessing only local memory:
       1 thread(s):
          total time : 1.062482699977977 s
    
    Each thread (in parallel) accessing the shared memory in read mode only:
       1 thread(s):
          total time : 1.083284599952492 s
    
    Each thread (in parallel) accessing the shared memory in write mode only:
       1 thread(s):
          total time : 0.9994508999643017 s
    
    -------------------------------------------------------------------------
    
    Each thread (in parallel) accessing only local memory:
       2 thread(s):
          total time : 1.039747699973645 s
    
    Each thread (in parallel) accessing the shared memory in read mode only:
       2 thread(s):
          total time : 1.096922599985817 s
    
    Each thread (in parallel) accessing the shared memory in write mode only:
       2 thread(s):
          total time : 1.058312799993644 s
    
    -------------------------------------------------------------------------
    
    Each thread (in parallel) accessing only local memory:
       3 thread(s):
          total time : 1.092771700024173 s
    
    Each thread (in parallel) accessing the shared memory in read mode only:
       3 thread(s):
          total time : 1.249695500044197 s
    
    Each thread (in parallel) accessing the shared memory in write mode only:
       3 thread(s):
          total time : 3.237864400054946 s
    
    -------------------------------------------------------------------------
    
    Each thread (in parallel) accessing only local memory:
       4 thread(s):
          total time : 1.112405900030012 s
    
    Each thread (in parallel) accessing the shared memory in read mode only:
       4 thread(s):
          total time : 1.426135099957548 s
    
    Each thread (in parallel) accessing the shared memory in write mode only:
       4 thread(s):
          total time : 5.475201099985327 s
    
    -------------------------------------------------------------------------
    
    Each thread (in parallel) accessing only local memory:
       5 thread(s):
          total time : 1.118396499949483 s
    
    Each thread (in parallel) accessing the shared memory in read mode only:
       5 thread(s):
          total time : 1.625696299968297 s
    
    Each thread (in parallel) accessing the shared memory in write mode only:
       5 thread(s):
          total time : 7.97176949995179 s
    
    -------------------------------------------------------------------------
    
    Each thread (in parallel) accessing only local memory:
       6 thread(s):
          total time : 1.123201800035986 s
    
    Each thread (in parallel) accessing the shared memory in read mode only:
       6 thread(s):
          total time : 1.780630800022664 s
    
    Each thread (in parallel) accessing the shared memory in write mode only:
       6 thread(s):
          total time : 11.44620599994391 s
    
    -------------------------------------------------------------------------
    
    In this example, the degradation appears from 3 threads in parallel.
Last edited by fxm on Jan 25, 2025 9:45, edited 4 times in total.
Reason: Post complemented.
Post Reply