CreateThread API - What happens under the bonnet?

I have a procedure that pings an IP address, eg: 192.168.0.1

If call this procedure 254 times in a loop structure, the loop takes nearly 4 minutes to complete.

If I use CreateThread 254 times, specifying the same procedure, it takes approx 4 seconds to complete.

I can understand the first method launches the procedure, waits for it to finish before moving onto the next address and repeating this (all very clean), but my schoolboy question is: what happens under the bonnet when using CreateThread, why don’t instances trample over each other causing corruption when the address of the procedure is the same each time ?

The CreateThread function itself does not too much, main points:

  • allocates and initializes Kernel and User internal structures to handle the new thread
  • allocates thread’s stack - reserves memory block of max stack size specified in the header of EXE and commits the part of this block having the size given as 2nd parameter of CreateThread or, if this parameter is 0, of the commit size from the EXE’s header
  • raises temporary priority level for the thread and allocates first quantum of the processor time to start the execution of the thread initialization routine
  • calls entry point of every DLL loaded into the memory space of process created the thread with the DLL_THREAD_ATTACH (2) parameter; this code is executing already on the thread stack
  • returns the priority level to normal level
  • invokes the thread function

Every thread has its own stack. Every thread has its own Windows structures. Every thread uses its own slots in the TLS (thread local storage) table. So, unless the function passed to CreateThread is accessing some shared resource without appropriate synchronization, there are no reasons for corrupting of anything.

3 Likes

Hello “Also”, great explanation, thank you.

In case readers think this topic is about START() I’ll add there is a Help topic from C6 named “Launching a Thread Behind the Scenes”. Its a good read for how Clarion’s START() works to do a CreateThread. It’s short so I’ll paste it:


With the advent of two new language statements supporting thread management in Clarion 6 (SUSPEND and RESUME), it is important to understand that there are a few things that are initialized and executed behind the scenes by the runtime library each time a thread is STARTed.

Here is the sequence of actions performed by the launching thread and the runtime library(RTL) each time a thread is STARTed:

  1. Launching Thread executes START(ThreadProc)
  2. RTL creates the physical thread in suspended state.
  3. RTL resumes the launched thread created in step 2.
  4. RTL sets an internal semaphore to a nonsignaled state.
  5. Launching Thread waits for the semaphore from the RTL.
  6. RTL creates instances of threaded variables and calls initialization routines for them.
  7. RTL sets the semaphore to signaled state.
  8. RTL suspends the launched thread creates in step 2.
  9. Launching Thread continues program execution.

The launching thread will continue until it encounters the ACCEPT statement. Upon execution of the ACCEPT statement:

  1. RTL resumes the launched thread.
  2. RTL calls the entry point of the ThreadProc.

Therefore, a launched thread will remain suspended until the next call to ACCEPT from the launching thread. Only initialization and constructors for threaded variables are executed.

The use of RESUME with the START statement immediately executes Step 10 above without waiting for the call to ACCEPT. In other words, use of RESUME with START does not depend on the ACCEPT statement for resuming thread execution. This allows a new thread to be started from windowless threads.

The same can be said by using the SUSPEND statement immediately after START, e.g., SUSPEND immediately stops thread execution and does not wait for the ACCEPT loop.


https://clarion.help/doku.php?id=launching_a_thread_behind_the_scenes.htm

https://clarion.help/doku.php?id=thread_model_faq.htm&s[]=thread

1 Like

There is one addition to these steps. Clarion-made DLLs are linked with the IDLL32.OBJ file. It includes the DLL’s entry. When the entry point called with the DLL_THREAD_ATTACH parameter, the execution is routing to the RTL function which is invoking initialization code for all THREADed data objects in this DLL.

Also, the code in IDLL32.OBJ (and in IEXE32.OBJ too) includes 2 functions which is calling to get the correct instance of a THREADed object referenced in some function.

2 Likes