Threads

The built-in procedures that create, and detach/wait-for the Threads.

Preamble:

   When a program starts executing, it has one implicit thread running 
   (this is already a full-fledged thread).
   This "main" thread executes the main function of the program.
   This same program can explicitly launch additional threads that will run 
   in a competitive manner (both between them and with the main thread).

   All threads (including the main thread) share the same memory, and thus 
   can access the same global variables, same heap memory, same set of file 
   descriptors, etc.
   All these threads execute in parallel (i.e. using time slices, or if the 
   system has several processors/cores, then really in parallel).

Creating a thread
   There are two methods to create a thread:
      - a "classic" method ThreadCreate that starts a specific user-defined 
      subroutine type (which has obligatorily one single parameter, an 'Any 
      Ptr' type pointer) in a separate execution thread, this first method 
      being 100% safe,
      - a "specific" method ThreadCall that should have start any 
      user-defined subroutine type (which may have almost any number and 
      any type of parameters) in a separate execution thread, but for the 
      moment this second method is bugged.

   Classic method (100% safe) - ThreadCreate
      - Syntax:
         Declare Function ThreadCreate ( ByVal procptr As Sub ( ByVal 
         userdata As Any Ptr ), ByVal param As Any Ptr = 0, 		ByVal 
         stack_size As Integer = 0 ) As Any Ptr
      - Usage:
         threadid = ThreadCreate ( procptr [, [ param ] [, stack_size ] ] )
      - Parameters:
         procptr
            A pointer to the Sub intended to work as a thread. The sub must 
            have the following signature (same parameters, same calling 
            convention) to be compatible to procptr:
               Declare Sub myThread ( ByVal userdata As Any Ptr )
         userdata
            The Any Ptr parameter of the Sub intended to work as a thread. 
            FreeBASIC expects this parameter to be present, it must not be 
            omitted! 
         param
            Any Ptr argument that will be passed to the thread Sub pointed 
            to by procptr through its userdata parameter. For example, this 
            can be a pointer to a structure or an array containing various 
            information for the thread sub to work with. If param is not 
            given, 0 (zero) will be passed to the thread sub's userdata 
            parameter instead.
         stack_size
            Optional number of bytes to reserve for this thread's stack.
      - Return value:
         The Any Ptr handle (threadid) to the thread created, or the null 
         pointer (0) on failure.

      Note:
         - The userdata parameter can be unused in the body of the myThread 
         sub, but declaring it as an Any Ptr parameter is always mandatory 
         in the header. In this case, the corresponding param parameter can 
         then be omitted when calling ThreadCreate, or else a needless 
         argument can still be passed ('0' is commonly used because this 
         value is directly compatible with any pointer). See the 1st and 
         2nd example.
         - In the case where data must be passed to myThread, the Any Ptr 
         param can be used to reference them, usually requiring a type 
         conversion (implicit or explicit) into Any Ptr before passing it 
         to ThreadCreate, and a reverse type conversion from Any Ptr in the 
         body of myThread before using it. See the 3rd example.

   Specific method (bugged) - ThreadCall
      - Syntax:
         Declare Function ThreadCall subname ( [paramlist] ) As Any Ptr
      - Usage:
         threadid = ThreadCall subname ( [paramlist] )
      - Parameters:
         subname
            The name of a subroutine
         paramlist
            A list of parameters to pass to the subroutine, as with a 
            normal sub call.	
      - Return value:
         The Any Ptr handle (threadid) to the thread created, or the null 
         pointer (0) on failure.

      Warning:
         Presently when ThreadCall involves to pass parameters to the 
         thread, there is no guarantee that the corresponding data are 
         still maintained after the end of the ThreadCall statement and 
         this until the thread is really launched. That can cause bad 
         behavior.
         Therefore it is more advisable for the moment to use ThreadCreate 
         (100% safe) instead.

   Description
      Several different threads can be created from the same Sub, with 
      different passed arguments allowing to define the behavior of each.

      There may be a long time between the end of the ThreadCreate/
      ThreadCall statement execution and the effective launch of the 
      thread. So some statements following the ThreadCreate/ThreadCall 
      statement can be executed before the actual launch of the thread.
      Conversely, the thread body can start executing even before 
      ThreadCreate/ThreadCall returns.

      There is no guarantee about the order in which different threads 
      execute, and no assumptions can be made about the order in which 
      multiple created threads actually start executing (except in Linux).

      By default, a thread is always created in the "joinable" state, ie 
      its handle is accessible by its 'threadid' identifier.
      If a thread ends in this state (joinable), the resources that were 
      assigned to it will not be released automatically (but only at the 
      main thread termination).

      So a good habit is to always use one and only one of the following 
      two methods for a thread to finish properly (see the paragraph 
      below):
         - either  waiting for the thread end,
         - otherwise detaching the thread (the thread becomes no longer 
         joinable).

      Each running thread can be identified by its handle which is unique 
      among all running threads.
      When a new thread is created, a handle to the thread is returned by 
      the creation function.
      When the thread runs code, ThreadSelf (from fbc version 1.08) allows 
      to also return the handle of the thread (the implicit main thread 
      also has its own unique handle).
      ThreadSelf may be used to code some sort of TLS (Thread Local 
      Storage) from the unique handle of each thread (including the 
      implicit main thread). Therefore, a same global variable name may be 
      defined, but with a stored value specific to the thread that accesses 
      it. This allows generic procedures to be coded, but with parameters 
      depending on the thread which executes them.

Waiting for a thread end, otherwise detaching a thread
   There are two methods to induce a proper thread termination:
      - either a first method ThreadWait where another thread waits for 
      this thread to finish,
      - otherwise a second method ThreadDetach where another thread 
      detaches this thread and continues.

   First method - ThreadWait
      - Syntax:
         Declare Sub ThreadWait ( ByVal threadid As Any Ptr )
      - Usage:
         ThreadWait ( threadid )
      - Parameters:
         threadid
            Any Ptr handle of a thread created by ThreadCreate or ThreadCall
      - Note:
         In other language (as C++), the 'wait()' suffix is called 'join()'
         .

   Second method - ThreadDetach
      - Syntax:
         Declare Sub ThreadDetach ( ByVal threadid As Any Ptr )
      - Usage:
         #include "fbthread.bi"
         ThreadDetach ( threadid )
      - Parameters:
         threadid
            Any Ptr handle of a thread created by ThreadCreate or ThreadCall

   Description
      After creating it, the programmer must make sure that the thread is 
      either waited for (joined) otherwise detached, and this from another 
      thread (including the main thread).

      ThreadWait waits for a thread to complete its execution, and then 
      release the resources associated with the thread handle. ThreadWait 
      does not return until the thread (designated by the identifier) ends.
      During the wait, the caller does not consume CPU time.
      ThreadWait does not force the thread to end. If a thread requires a 
      signal to force an end, a mechanism such as a shared flag must be 
      used.

      ThreadDetach releases resources associated with the thread handle. 
      The thread handle will be destroyed by ThreadDetach and can no longer 
      be used.
      Unlike ThreadWait, ThreadDetach does not wait for the end of the 
      thread, and its execution continues independently. All allocated 
      resources will be freed once the thread is complete.

      After ThreadWait or ThreadDetach is applied, the thread can no longer 
      be joined, so the handle identifier value must not be used again in 
      any of these commands.

      Generally, before finishing, a 'parent' thread is waiting for the 
      'child' thread to finish.
      But if the programmer chooses not to wait until the end of the thread 
      (and necessarily detaches it only), then he must make sure that the 
      data accessed by that thread is valid until the thread has finished 
      with it. Otherwise, one may encounter a situation where the 'parent' 
      thread holds pointers/references to local variables and the 'child' 
      thread hasn't finished when the 'parent' thread finishes (the 
      variables being destroyed because becoming out of scope).

Example
   The 'Main' thread displays ten "M" characters while the 'Child' thread 
   simultaneously displays ten "C" characters.
   A 'Sleep x, 1' tempo is put in the 'For' loop of each thread (main 
   thread and child thread) to release time-slice allowing the other thread 
   to execute as well.
   The tempos are set so that the execution time of the child thread 'For' 
   loop is greater than the one of the main thread 'For' loop.

      - Using ThreadCreate ..... ThreadWait:
   Declare Sub thread (ByVal userdata As Any Ptr)

   Dim As Any Ptr threadID  '' declaration of an 'Any Ptr' thread-ID of the child thread

   Print """M"": from 'Main' thread"
   Print """C"": from 'Child' thread"
   Print

   threadID = ThreadCreate(@thread)  '' creation of the child thread from the main thread

   For I As Integer = 1 To 10  '' 'For' loop of the main thread
      Print "M";
      Sleep 150, 1
   Next I

   ThreadWait(threadID)  '' waiting for the child thread termination
   Print
   Print "'Child' thread finished"

   Sleep

   Sub thread (ByVal userdata As Any Ptr)  '' sub executed by the child thread
      For I As Integer = 1 To 10          '' 'For' loop of the child thread
         Print "C";
         Sleep 350, 1
      Next I
   End Sub
            
Output example:

   "M": from 'Main' thread
   "C": from 'Child' thread

   MCMMCMMCMMCMMMCCCCCC
   'Child' thread finished
   				

      - Using ThreadCreate + ThreadDetach ..... (a global end-flag is added 
      at the end of the child thread):
   #include "fbthread.bi"

   Declare Sub thread (ByVal userdata As Any Ptr)

   Dim As Any Ptr threadID          '' declaration of an 'Any Ptr' thread-ID of the child thread
   Dim Shared As Boolean threadEnd  '' declaration of a global 'Boolean' thread-End flag for the child thread

   Print """M"": from 'Main' thread"
   Print """C"": from 'Child' thread"
   Print

   threadID = ThreadCreate(@thread)  '' creation of the child thread from the main thread
   ThreadDetach(threadID)            '' detaching the child thread

   For I As Integer = 1 To 10  '' 'For' loop of the main thread
      Print "M";
      Sleep 150, 1
   Next I

   While threadEnd = False  '' waiting for the thread-End flag = 'True' from the child thread
   Wend
   Print
   Print "'Child' thread finishing or finished"

   Sleep

   Sub thread (ByVal userdata As Any Ptr)  '' sub executed by the child thread
      For I As Integer = 1 To 10          '' 'For' loop of the child thread
         Print "C";
         Sleep 350, 1
      Next I
      threadEnd = True                    '' set the thrend-End flag to 'True'
   End Sub
            
Output example:

   "M": from 'Main' thread
   "C": from 'Child' thread

   MCMMCMMCMMCMMMCCCCCC
   'Child' thread finishing or finished
   				

   A UDT for a multi-timer feature:
      - using internally a joinable thread (ThreadCreate ..... ThreadWait) 
      to sequence each timer,
      - and call-backing externally a detached thread (ThreadCreate + 
      ThreadDetach .....) as event for user.
   The user event being triggered by a detached-thread callback from the 
   timer loop, the requested time-out is only biased by the execution time 
   of ThreadCreate + ThreadDetach (small time about constant) and not by a 
   ThreadWait waiting-for:
   '    Only 4 member procedures in public access (the first 3 returning 'true' if success, 'false' else):
   '        - Function 'Set' to parametrize the considered timer (time-out in ms, pointer to user thread)
   '        - Function 'Start' to start the considered timer
   '        - Function 'Stop' to stop the considered timer (then, the considered timer may be re-Set and re-Start)
   '        - Property 'Counter' to get the occurrence number of the timer
   '    Plus an 'Any Ptr' in public access:
   '        - Pointer field 'userdata' to point to any user data structure (optional usage)
   '
   '    Remark:
   '        - Pointer to the considered timer instance is provided to the user thread procedure
   '          in order to be able to factorize the treatment per timers group,
   '          and to address the right user data structure if used (see example for usage).
   '
   '    In private access:
   '        - 4 internal variables (time-out value, pointer to user thread, handle to timer thread, counter of occurence)
   '        - Static timer thread

   #include "fbthread.bi"
   Type UDT_timer_thread
      Public:
         Declare Function Set (ByVal time_out As UInteger, _
                          ByVal timer_procedure As Sub(ByVal param As Any Ptr)) _
                          As Boolean
         Declare Function Start () As Boolean
         Declare Function Stop () As Boolean
         Declare Property Counter () As UInteger
         Dim As Any Ptr userdata
      Private:
         Dim As UInteger tempo
         Dim As Sub(ByVal param As Any Ptr) routine
         Dim As Any Ptr handle
         Dim As UInteger count
         Declare Static Sub thread (ByVal param As Any Ptr)
   End Type

   Function UDT_timer_thread.Set (ByVal time_out As UInteger, _
                          ByVal timer_procedure As Sub(ByVal param As Any Ptr)) _
                          As Boolean
      If timer_procedure > 0 And This.handle = 0 Then
         This.tempo = time_out
         This.routine = timer_procedure
         This.count = 0
         Function = True
      Else
         Function = False
      End If
   End Function

   Function UDT_timer_thread.Start () As Boolean
      If This.handle = 0 And This.routine > 0 Then
         This.handle = ThreadCreate(@UDT_timer_thread.thread, @This)
         Function = True
      Else
         Function = False
      End If
   End Function

   Function UDT_timer_thread.Stop () As Boolean
      If This.handle > 0 Then
         Dim p As Any Ptr = 0
         Swap p, This.handle
         ThreadWait(p)
         Function = True
      Else
         Function = False
      End If
   End Function

   Property UDT_timer_thread.Counter () As UInteger
      Return This.count
   End Property

   Static Sub UDT_timer_thread.thread (ByVal param As Any Ptr)
      Dim As UDT_timer_thread Ptr pu = param
      While pu->handle > 0
         Sleep pu->tempo, 1
         pu->count += 1
         If pu->routine > 0 Then
            Dim As Any Ptr p = ThreadCreate(Cast(Any Ptr, pu->routine), param)
            ThreadDetach(p)
         End If
      Wend
   End Sub

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

   Dim As UInteger tempo1 = 950
   Dim As UInteger tempo2 = 380
   Dim As UDT_timer_thread timer1
      timer1.userdata = New String("        callback from timer #1 (" & tempo1 & "ms)")
   Dim As UDT_timer_thread timer2
      timer2.userdata = New String("        callback from timer #2 (" & tempo2 & "ms)")

   Sub User_thread (ByVal param As Any Ptr)
      Dim As UDT_timer_thread Ptr pu = param
      Dim As String Ptr ps = pu->userdata
      Print *ps & ", occurrence: " & pu->Counter
   End Sub

   Print "Beginning of test"
   If timer1.Set(tempo1, @User_thread) Then
      Print "    timer #1 set OK"
      If timer1.Start Then
         Print "        timer #1 start OK"
      End If
   End If
   If timer2.Set(tempo2, @User_thread) Then
      Print "    timer #2 set OK"
      If timer2.Start Then
         Print "        timer #2 start OK"
      End If
   End If
   Print "    Then, any key to stop the timers"

   Sleep

   If timer1.stop Then
      Print "    timer #1 stop OK"
   End If
   If timer2.stop Then
      Print "    timer #2 stop OK"
   End If
   Sleep 500, 1
   Print "End of test"
   Delete Cast(String Ptr, timer1.userdata)
   Delete Cast(String Ptr, timer2.userdata)

   Sleep
         
Output example:

   Beginning of test
   	Timer #1 set OK
   		Timer #1 start OK
   	Timer #2 set OK
   		Timer #2 start OK
   	Then, Any key To Stop the timers
   		callback from Timer #2 (380ms), occurrence: 1
   		callback from Timer #2 (380ms), occurrence: 2
   		callback from Timer #1 (950ms), occurrence: 1
   		callback from Timer #2 (380ms), occurrence: 3
   		callback from Timer #2 (380ms), occurrence: 4
   		callback from Timer #1 (950ms), occurrence: 2
   		callback from Timer #2 (380ms), occurrence: 5
   		callback from Timer #2 (380ms), occurrence: 6
   		callback from Timer #2 (380ms), occurrence: 7
   		callback from Timer #1 (950ms), occurrence: 3
   		callback from Timer #2 (380ms), occurrence: 8
   		callback from Timer #2 (380ms), occurrence: 9
   		callback from Timer #1 (950ms), occurrence: 4
   		callback from Timer #2 (380ms), occurrence: 10
   		callback from Timer #2 (380ms), occurrence: 11
   		callback from Timer #2 (380ms), occurrence: 12
   	Timer #1 Stop OK
   		callback from Timer #1 (950ms), occurrence: 5
   	Timer #2 Stop OK
   		callback from Timer #2 (380ms), occurrence: 13
   End of test
   			

See also
   * ThreadCreate, ThreadCall
   * ThreadSelf
   * ThreadWait, ThreadDetach
   * Multi-Threading Overview
   * Mutual Exclusion
   * Conditional Variables
   * Critical Sections
   * Critical Sections FAQ

