Execution timing of a Sub launched as a thread

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

Re: Execution timing of a Sub launched as a thread

Post by fxm »

Interesting to see the Tmin and Tmax between the executing start of thread body and the returning point of Threadcreate():

Code: Select all

Dim As Any Ptr ptid
Dim As Double t0
Dim As Any Ptr p0 = @t0
Dim As Double t1
Dim As Single tmin = 10   '' start value
Dim As Single tmax = -10  '' start value

Sub myThread (Byval p As Any Ptr)
    *Cast(Double Ptr, p) = Timer
End Sub

Do
    ptid = Threadcreate(@myThread, @t1)
    *Cast(Double Ptr, p0) = Timer
   
    Threadwait(ptid)
   
    If t1 - t0 < tmin Or t1 - t0 > tmax Then
        If t1 - t0 < tmin Then
            tmin = t1 - t0
        End If
        If t1 - t0 > tmax Then
            tmax = t1 - t0
        End If
        Print Time,"Tmin="; tmin * 1000; " ms", "Tmax="; tmax * 1000; " ms"
    End If
Loop Until Inkey <> ""
07:41:25 Tmin= 0.0873 ms Tmax= 0.0873 ms
07:41:25 Tmin= 0.0873 ms Tmax= 0.1399 ms
07:41:25 Tmin= 0.07799999 ms Tmax= 0.1399 ms
07:41:25 Tmin= 0.07799999 ms Tmax= 0.6494 ms
07:41:25 Tmin= 0.07229999 ms Tmax= 0.6494 ms
07:41:25 Tmin= 0.056 ms Tmax= 0.6494 ms
07:41:25 Tmin= 0.0557 ms Tmax= 0.6494 ms
07:41:25 Tmin= 0.0501 ms Tmax= 0.6494 ms
.....
.....
07:41:43 Tmin= 0.0106 ms Tmax= 2.1956 ms
07:42:03 Tmin= 0.0028 ms Tmax= 2.1956 ms
07:42:23 Tmin= 0.0028 ms Tmax= 3.3545 ms
07:42:51 Tmin=-0.0318 ms Tmax= 3.3545 ms
07:42:55 Tmin=-0.0498 ms Tmax= 3.3545 ms
07:44:19 Tmin=-0.0498 ms Tmax= 7.2985 ms
07:51:04 Tmin=-0.0659 ms Tmax= 7.2985 ms
07:58:26 Tmin=-0.0839 ms Tmax= 7.2985 ms
Roland Chastain
Posts: 993
Joined: Nov 24, 2011 19:49
Location: France
Contact:

Re: Execution timing of a Sub launched as a thread

Post by Roland Chastain »

SARG wrote:On Windows there is a parameter to not let the new thread running immediately but that requires to execute an extra statement for starting it.
So there is no way do that in a multiplatform program?

In Free Pascal, you can create a thread with a boolean parameter CreateSuspended, and after that you have a Start method. If I understand well, in FreeBASIC thread creation and thread start are the same operation.

In my chess GUI I included a boolean value in the thread type, to activate or deactivate the thread. When the value is false, the thread is still active but does nothing except waiting. But while doing that I was wondering if there wasn't a better solution, to separate thread execution from thread creation.
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Execution timing of a Sub launched as a thread

Post by fxm »

I think the most general and safe solution in FreeBASIC is to use mutual exclusion between the Threadcreate() line and the start of the thread body, as suggested above.

Principle:

Code: Select all

Dim Shared As Any Ptr pMutexForThreadStart

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

Sub Thread (Byval p As Any Ptr)
    Mutexlock(pMutexForThreadStart)
    Mutexunlock(pMutexForThreadStart)
    
    ' user thread body
    
End Sub

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

' user main code

pMutexForThreadStart = Mutexcreate()

' user main code continues

Mutexlock(pMutexForThreadStart)
Dim As Any Ptr pThread = Threadcreate(@Thread)
' lines of code to be executed before the executing start of the user body of the thread
' (for example, using the value of pThread)
Mutexunlock(pMutexForThreadStart)

' user main code continues

Threadwait(pThread)
MutexDestroy(pMutexForThreadStart)
Example with the failed assignment test (from above):

Code: Select all

Dim As Any Ptr ptid
Dim Shared As Any Ptr pMutexForThreadStart

Sub myThread (Byval p As Any Ptr)
    Mutexlock(pMutexForThreadStart)
    Mutexunlock(pMutexForThreadStart)
    Dim As Any Ptr ptid = *Cast(Any Ptr Ptr, p)
    If ptid = 0 Then
        Print Time; " the assignment 'ptid = Threadcreate(@myThread, @ptid)' is not yet made"
    End If
End Sub

Print Time
pMutexForThreadStart = Mutexcreate()
Do
    ptid = 0
    Mutexlock(pMutexForThreadStart)
    ptid = Threadcreate(@myThread, @ptid)
    Mutexunlock(pMutexForThreadStart)
    Threadwait(ptid)
Loop Until Inkey <> ""

MutexDestroy(pMutexForThreadStart)
No more fault is displayed.

If one suppresses the mutex locking in the code, then the faults come back.
Last edited by fxm on Oct 21, 2020 9:02, edited 1 time in total.
SARG
Posts: 1756
Joined: May 27, 2005 7:15
Location: FRANCE

Re: Execution timing of a Sub launched as a thread

Post by SARG »

@Roland,
On Windows, threadcreate calls the Windows API createthread setting the creation flags (see below) null so no direct way. I guess that FreePascal uses the boolean parameter to set the flag.

"Creation flags that control the creation of the thread. If the CREATE_SUSPENDED flag is specified, the thread is created in a suspended state, and will not run until the ResumeThread function is called. If this value is zero, the thread runs immediately after creation. At this time, no other values are supported."

On Linux I know less. It seems, after a quick search, that a such parameter doesn't exist but some tricks are obviously possible.

So afaik to be multiplatform you'll need to use OS's API and conditionnal compilation.
However using a mutex should solve your problem.

fxm faster.....
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Re: Execution timing of a Sub launched as a thread

Post by D.J.Peters »

There are more things to note aboute threads at all.

On Windows if you create 10 "same" threads in a loop the order of execution looks random for me.
10,1,3,4,5,9,2,6,7,8

On Linux it's the right order:
1,2,3,4,5,6,7,8,9,10

If you use threads in a FreeBASIC shared lib (Linux and Windows)
before you unload the dynamic lib via (DyLibFree handle) from memory.
You stop all threads and use WaitThread than the modul destructor ends.
Sometimes the debug message (realy on end) of the module destructor are printed and later the debug message of end of thread's are printed !
Note the wrong order !
(Normally you would think the last line of the modul destructor are the last line before the dynmic lib are removed from memory)
...

Joshy
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Execution timing of a Sub launched as a thread

Post by fxm »

Example of 'ThreadInitThenStart' UDT structure:

Code: Select all

Type ThreadInitThenStart
    Public:
        Declare Function ThreadInit(Byval pThread As Sub(Byval As Any Ptr), Byval p As Any Ptr = 0) As Any Ptr
        Declare Sub ThreadStart()
    Private:
        Dim As Sub(Byval p As Any Ptr) _pThread
        Dim As Any Ptr _p
        Dim As Any Ptr _mutex
        Declare Static Sub _Thread(Byval p As Any Ptr)
End Type

Function ThreadInitThenStart.ThreadInit(Byval pThread As Sub(Byval As Any Ptr), Byval p As Any Ptr = 0) As Any Ptr
    If This._mutex = 0 Then
        This._mutex = Mutexcreate()
        Mutexlock(This._mutex)
    End If
    This._pThread = pThread
    This._p = p
    Return Threadcreate(@ThreadInitThenStart._Thread, @This)
End Function

Sub ThreadInitThenStart.ThreadStart()
    If This._mutex > 0 Then
        Mutexunlock(This._mutex)
    End If
End Sub

Sub ThreadInitThenStart._Thread(Byval p As Any Ptr)
    Dim As ThreadInitThenStart Ptr pThis = p
    Mutexlock(pThis->_mutex)
    Mutexunlock(pThis->_mutex)
    Mutexdestroy(pThis->_mutex)
    pThis->_mutex = 0
    pThis->_pThread(pThis->_p)
End Sub

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

Sub UserThread (Byval p As Any Ptr)
    Dim As Zstring Ptr pz = p
    Print *pz
End Sub

Dim As ThreadInitThenStart t1
Dim As Any Ptr pt1 = t1.ThreadInit(@UserThread, @"user thread code body starts")  '' initializes the user thread

Sleep
t1.ThreadStart()  '' starts the user thread code body

Threadwait(pt1)   '' waits for the thread end
Sleep
[edit]
See the principle here.

[edit]
Another Type with a little more advanced structure, and where the user can easily pass own data to the thread(s) by extending the Type with own data members (also allowing thread local storage capability) and even adding own procedure members:

Code: Select all

Type MTLSinterface                                            '' interface for Multi-Threading with Local-Storage
    Public:                                                   ''    capability for user by extending the Type
        Declare Sub MTLSinit(Byval As Sub(Byval As Any Ptr))  '' initialize the thread without start the user sub body
        Declare Sub MTLSstart()                               '' start the user sub body (thread initialized previously)
        Declare Sub MTLSwait()                                '' wait for the thread end
        Declare Sub MTLSdetach()                              '' detach the thread
        Declare Property MTLSrunning() As Boolean             '' running state of the user body
    Private:
        Dim As Sub(Byval As Any Ptr) MTLS_psub                '' internal pointer tu the user sub
        Dim As Any Ptr MTLS_pthread                           '' internal pointer returned by threadcreate
        Dim As Any Ptr MTLS_pmutex                            '' internal pointer returned by mutexcreate
        Dim As Boolean MTLS_running                           '' internal running state of the user sub body
        Dim As Uinteger MTLS_index                            '' internal thread index
        Declare Static Sub MTLS_thread(Byval As Any Ptr)      '' internal thread sub
End Type

Sub MTLSinterface.MTLSinit(Byval pSUB As Sub (Byval As Any Ptr))
    This.MTLS_psub = pSUB
    This.MTLS_pmutex = Mutexcreate()
    Mutexlock(This.MTLS_pmutex)
    This.MTLS_pThread = Threadcreate(@MTLSinterface.MTLS_thread, @This)
End Sub

Sub MTLSinterface.MTLSstart()
    Mutexunlock(This.MTLS_pmutex)
End Sub

Sub MTLSinterface.MTLSwait()
    Threadwait(This.MTLS_pThread)
End Sub

#include once "fbthread.bi"
Sub MTLSinterface.MTLSdetach()
    Threaddetach(This.MTLS_pThread)
End Sub

Property MTLSinterface.MTLSrunning() As Boolean
    Return This.MTLS_running
End Property

Sub MTLSinterface.MTLS_thread(Byval p As Any Ptr)
    Dim As MTLSinterface Ptr pMTLS = p
    Mutexlock(pMTLS->MTLS_pmutex)
    Mutexunlock(pMTLS->MTLS_pmutex)
    Mutexdestroy(pMTLS->MTLS_pmutex)
    pMTLS->MTLS_running = True
    pMTLS->MTLS_psub(pMTLS)
    pMTLS->MTLS_running = False
End Sub

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

Type myMTLSinterface Extends MTLSinterface
    Dim As String prefix
    Dim As String suffix
    Dim As Double tempo
    Dim As Uinteger counter
    Static As Any Ptr mutex
End Type
Dim As Any Ptr myMTLSinterface.mutex

myMTLSinterface.mutex = Mutexcreate()

Function count(Byval pcounter As Uinteger Ptr) As Uinteger
    *pcounter += 1
    Return *pcounter
End Function

Sub Thread(Byval p As Any Ptr)
    Dim As myMTLSinterface Ptr pMTLS = p
    Dim As Uinteger c
    Do
        c = count(@pMTLS->counter)
        Mutexlock(myMTLSinterface.mutex)
        Print pMTLS->prefix;
        Print c;
        Print pMTLS->suffix;
        Mutexunlock(myMTLSinterface.mutex)
        Sleep pMTLS->tempo, 1
    Loop Until c = 8
End Sub

Print "|x| : counting (x) thread a"
Print "(x) : counting (x) thread b"
Print "[x] : counting (x) thread c"
Print

Dim As myMTLSinterface mtlsa
mtlsa.prefix = "|"
mtlsa.suffix = "|"
mtlsa.MTLSinit(@thread)
mtlsa.tempo = 250
mtlsa.MTLSstart()

Dim As myMTLSinterface mtlsb
mtlsb.prefix = "("
mtlsb.suffix = ")"
mtlsb.MTLSinit(@thread)
mtlsb.tempo = 150
mtlsb.MTLSstart()

Dim As myMTLSinterface mtlsc
mtlsc.prefix = "["
mtlsc.suffix = "]"
mtlsc.MTLSinit(@thread)
mtlsc.tempo = 100
mtlsc.MTLSstart()

mtlsa.MTLSwait()
mtlsb.MTLSwait()
mtlsc.MTLSwait()
Print
Print
Print "end of threads"
Mutexdestroy(myMTLSinterface.mutex)

Sleep
Last edited by fxm on Jan 29, 2021 6:19, edited 7 times in total.
Roland Chastain
Posts: 993
Joined: Nov 24, 2011 19:49
Location: France
Contact:

Re: Execution timing of a Sub launched as a thread

Post by Roland Chastain »

fxm wrote:Example of 'ThreadInitThenStart' UDT structure:
Great! I will certainly reuse it (and also your previous example).

@SARG
Thank you for explanations.
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Execution timing of a Sub launched as a thread

Post by fxm »

D.J.Peters wrote:On Windows if you create 10 "same" threads in a loop the order of execution looks random for me.
10,1,3,4,5,9,2,6,7,8

On Linux it's the right order:
1,2,3,4,5,6,7,8,9,10
Already reported in the documentation (see "Plateform Differences" in "ThreadCreate" page).
D.J.Peters wrote:If you use threads in a FreeBASIC shared lib (Linux and Windows)
before you unload the dynamic lib via (DyLibFree handle) from memory.
You stop all threads and use WaitThread than the modul destructor ends.
Sometimes the debug message (realy on end) of the module destructor are printed and later the debug message of end of thread's are printed !
Note the wrong order !
(Normally you would think the last line of the modul destructor are the last line before the dynmic lib are removed from memory)
Not very clear to me.
If you are concerned about the subject, can you post a short code (main code + dll code) that shows the behavior which seems random/abnormal to you.
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Execution timing of a Sub launched as a thread

Post by fxm »

D.J.Peters wrote:On Windows if you create 10 "same" threads in a loop the order of execution looks random for me.
10,1,3,4,5,9,2,6,7,8

On Linux it's the right order:
1,2,3,4,5,6,7,8,9,10
Similarly to the two above mutex usages to handle the execution start of thread body:
- starting always after Threadcreate() return,
- starting only after another explicit command,
another mutex usage also allows to:
- ordering in a wanted way the launch sequence of several threads:

Code: Select all

Dim Shared As Any Ptr pm
Dim As Integer nbt = 28
Dim As Any Ptr pt(1 To nbt)

Sub ThreadUnorder (Byval p As Any Ptr)
    Print " " & p;
End Sub

Sub ThreadOrder (Byval p As Any Ptr)
    Print " " & p;
    Mutexunlock(pm)
End Sub

Print "Unordered launch of threads:"
For n As Integer = 1 To nbt
    pt(n) = Threadcreate (@ThreadUnorder, Cast(Any Ptr, n))
Next n
For n As Integer = 1 To nbt
    Threadwait(pt(n))
Next n
Print
Print

pm = Mutexcreate()
Print "Ordered launch of threads:"
For n As Integer = 1 To nbt
    Mutexlock(pm)
    pt(n) = Threadcreate (@ThreadOrder, Cast(Any Ptr, n))
Next n
For n As Integer = 1 To nbt
    Threadwait(pt(n))
Next n
Mutexdestroy(pm)
Print
Print

Sleep
Unordered launch of threads:
1 2 3 5 4 6 26 7 13 10 11 12 9 14 19 17 16 18 24 15 20 21 22 23 25 8 27 28

Ordered launch of threads:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Roland Chastain
Posts: 993
Joined: Nov 24, 2011 19:49
Location: France
Contact:

Re: Execution timing of a Sub launched as a thread

Post by Roland Chastain »

Works perfectly. My test on Linux:
Unordered launch of threads:
1 3 2 8 7 9 10 11 4 5 13 6 15 16 17 18 19 20 21 22 23 24 25 26 27 12 14 28

Ordered launch of threads:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Execution timing of a Sub launched as a thread

Post by fxm »

On the other hand, without any mutex, we can create nested threads:

Code: Select all

Sub Thread (Byval p As Any Ptr = 0)
    Print "starting the body execution of thread #" & p
    If p > 1 Then
        Print "   creation of thread #" & p - 1
        Dim As Any Ptr pt = Threadcreate(@Thread, p - 1)
        
        Print "   waiting for end of thread #" & p - 1
        Threadwait(pt)
        
        Print "   end of thread #" & p - 1
    End If
    Print "finishing the body execution of thread #" & p
End Sub

Print "starting the body execution of main code"
Print "   creation of thread #" & 4
Dim As Any Ptr pt = Threadcreate(@Thread, (Cast(Any Ptr, 4)))

Print "   waiting for end of thread #" & 4
Threadwait(pt)

Print "   end of thread #" & 4
Print "finishing the body execution of main code"

Sleep

Code: Select all

starting the body execution of main code
   creation of thread #4
   waiting for end of thread #4
starting the body execution of thread #4
   creation of thread #3
   waiting for end of thread #3
starting the body execution of thread #3
   creation of thread #2
   waiting for end of thread #2
starting the body execution of thread #2
   creation of thread #1
   waiting for end of thread #1
starting the body execution of thread #1
finishing the body execution of thread #1
   end of thread #1
finishing the body execution of thread #2
   end of thread #2
finishing the body execution of thread #3
   end of thread #3
finishing the body execution of thread #4
   end of thread #4
finishing the body execution of main code
Post Reply