CreateThread() vs. CreateVOThread()

Public support forum for peer to peer support with related to the Visual Objects and Vulcan.NET products
Post Reply
robert.pope
Posts: 12
Joined: Tue May 02, 2017 6:14 am

CreateThread() vs. CreateVOThread()

Post by robert.pope »

(This is basically a copy+paste of my question over on comp.lang.clipper.visual-objects. The guys over there suggested I bring it here to try and get Robert's attention.)

Hi All,

I'm wondering if anyone knows what the differences between Win32 API CreateThread() vs. VO's CreateVOThread() are? Mostly with relation to the garbage collector. I.e. if I use Win32 API's CreateThread() would I completely avoid the garbage collector running on my thread. Is there a reason why I would still be better off using CreateVOThread()?

I understand that threads and dynamic memory / the garbage collector don't play well together. I have in my ThreadFunc avoided declaring any LOCAL's and avoided calling any VO functions (because they might also have LOCAL's). There are also no anonymous variables like a string concatenation. Where I can't avoid a variable I use a structure (ThreadData) in static memory. The thread is the only reader/writer to this structure.

Assuming I haven't missed anything, my thread is only working in static memory and only calling Windows API functions.

The thread itself is pretty simple. It calls Win32 API IsHungAppWindow() once per second and logs an event in the windows event log if that function returns true.

I'm not sure how much I trust VO's threading (or my code not to have missed some dynamic memory allocation somewhere) so I've also included runtime options to completely disable this feature if it plays up.

Cheers!
Rob.

I guess I will post the code of my thread func, given the responses I'm getting :)
And the question I'm trying to have answered: Would the garbage collector ever initiate on this thread if I start it with CreateThread()? I don't want it to.

STRUCT HeartBeatMonitor_ThreadData

MEMBER hWnd AS PTR
MEMBER hEventSource AS PTR
MEMBER pszEventSource AS PSZ
MEMBER RootDirectory AS PSZ

MEMBER sTimeStart AS _winSYSTEMTIME
MEMBER sTimeEnd AS _winSYSTEMTIME

FUNCTION HeartbeatMonitor(sHeartBeatMonitor_ThreadData AS HeartBeatMonitor_ThreadData) AS DWORD PASCAL

// Do not declare any locals here. Garbage collector is not thread safe.
// Also must not call any functions including VO builtins that might allocate memory.

DO WHILE TRUE
IF sHeartBeatMonitor_ThreadData.hWnd != NULL_PTR

IF IsHungAppWindow(sHeartBeatMonitor_ThreadData.hWnd)
IF sHeartBeatMonitor_ThreadData.sTimeStart == NULL_PTR

sHeartBeatMonitor_ThreadData.sTimeStart := MemAlloc(_SizeOf(_winSYSTEMTIME))
GetLocalTime(sHeartBeatMonitor_ThreadData.sTimeStart)

sHeartBeatMonitor_ThreadData.hEventSource := RegisterEventSource(NULL_PSZ, sHeartBeatMonitor_ThreadData.pszEventSource)
ReportEvent(sHeartBeatMonitor_ThreadData.hEventSource, EVENTLOG_WARNING_TYPE, 0, MSG_APPLICATION_HANG, NULL_PTR, 1, 0, @sHeartBeatMonitor_ThreadData.RootDirectory, NULL_PTR)
DeregisterEventSource(sHeartBeatMonitor_ThreadData.hEventSource)
sHeartBeatMonitor_ThreadData.hEventSource := NULL_PTR

END IF
ELSE
IF sHeartBeatMonitor_ThreadData.sTimeStart != NULL_PTR

sHeartBeatMonitor_ThreadData.sTimeEnd := MemAlloc(_SizeOf(_winSYSTEMTIME))
GetLocalTime(sHeartBeatMonitor_ThreadData.sTimeEnd)

sHeartBeatMonitor_ThreadData.hEventSource := RegisterEventSource(NULL_PSZ, sHeartBeatMonitor_ThreadData.pszEventSource)
ReportEvent(sHeartBeatMonitor_ThreadData.hEventSource, EVENTLOG_INFORMATION_TYPE, 0, MSG_APPLICATION_RESUME, NULL_PTR, 1, 0, @sHeartBeatMonitor_ThreadData.RootDirectory, NULL_PTR)
DeregisterEventSource(sHeartBeatMonitor_ThreadData.hEventSource)
sHeartBeatMonitor_ThreadData.hEventSource := NULL_PTR

MemFree(sHeartBeatMonitor_ThreadData.sTimeStart)
MemFree(sHeartBeatMonitor_ThreadData.sTimeEnd)

sHeartBeatMonitor_ThreadData.sTimeStart := NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeEnd := NULL_PTR

END IF
END IF

END IF

Sleep(1000)

END DO

RETURN 0



This function runs on the main thread to launch the thread:
FUNCTION HeartbeatMonitorInit(hWnd AS PTR) AS VOID PASCAL

LOCAL sSECURITY_ATTRIBUTES AS _winSECURITY_ATTRIBUTES
LOCAL sHeartBeatMonitor_ThreadData AS HeartBeatMonitor_ThreadData

sSECURITY_ATTRIBUTES := MemAlloc(_SizeOf(_winSECURITY_ATTRIBUTES))
sSECURITY_ATTRIBUTES.nLength := _SizeOf(_winSECURITY_ATTRIBUTES)
sSECURITY_ATTRIBUTES.lpSecurityDescriptor := NULL_PTR
sSECURITY_ATTRIBUTES.bInheritHandle := FALSE

sHeartBeatMonitor_ThreadData := MemAlloc(_SizeOf(HeartBeatMonitor_ThreadData))
sHeartBeatMonitor_ThreadData.hWnd := hWnd
sHeartBeatMonitor_ThreadData.pszEventSource := PszAlloc(String2PSZ("XXXXX"))
sHeartBeatMonitor_ThreadData.RootDirectory := PszAlloc(String2PSZ(gcRootDirectory))
sHeartBeatMonitor_ThreadData.sTimeStart := NULL_PTR
sHeartBeatMonitor_ThreadData.sTimeEnd := NULL_PTR

CreateThread(sSECURITY_ATTRIBUTES, 0, @HeartbeatMonitor(), sHeartBeatMonitor_ThreadData, 0, NULL_PTR)

MemFree(sSECURITY_ATTRIBUTES)

RETURN

Cheers!
User avatar
Chris
Posts: 4562
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

CreateThread() vs. CreateVOThread()

Post by Chris »

Hi Robert,

If I understand it correctly, CreateVOThread() makes sure to avoid issues with the Garbage Collector for the moment the new thread is created, so this function should always be used in VO, instead of simply calling CreateThread(). But Robert (the other one :)) will correct me if I'm wrong when he's available (he's currently out of the office for a few days).

Regarding the GC in general, declaring for example an INT or LOGIC (or even REAL) LOCAL variable will not affect it at all, those are stored in the stack and produce no GC activity. But if you instantiate classes, use FLOATS etc, then indeed those will cause GC activity.

I'm not sure if it's that bad though, are there indeed that many problems with threads and the GC in VO? I admit I had never used threads myself in VO..
Chris Pyrgas

XSharp Development Team test
chris(at)xsharp.eu
User avatar
robert
Posts: 4225
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

CreateThread() vs. CreateVOThread()

Post by robert »

Robert,
I am answering this from my holiday address and from memory so I am not absolutely sure, but this is what I remember from the VO runtime:

There is not much difference between CreateVOThread() and CreateThread().
Each new thread gets its own stack space from the OS but the VO runtime also uses some "static" data per thread such as the address of the dynamic memory pages and for example the current line number in the code that is executing. Each thread also needs dynamic memory pages.

If you use CreateThread() then the first time the code registers a line number with the runtime, the runtime will detect that a new thread has started and will allocate the memory needed by the thread..
If you use CreateVOThread() then the memory is allocated before the actual thread has started.

IIRC ExitVOThread() just calls ExitThread(). So there is no difference (in older versions of VO there was).
The real "clean up" of memory allocated by a thread happens when the OS sends a DLL_THREAD_DETACH method to the dllmain function inside the runtime DLL.(https://docs.microsoft.com/en-us/window ... ls/dllmain)

But the most important question of all is not what these functions do but why you need multi threading.
In VO I personally would NEVER use Multi Threading. The dynamic memory management is simply not stable enough in a multi thread environment.
If you are absolutely sure that your code does not use any dynamic memory then you may be safe.
And at first sight your code seems to fit in that.

But I have personally never seen a situation that absolutely required MT programming.
In most cases it was better to delegate the work that the thread has to do to a separate program that is called by the main program.
Your code to check for an app that hangs can also be delegated to a separate app.
The only difficulty then is that you need a mechanism to close that app when the main app closes. But there are many ways to do that. For example by using a semaphore file or using a shared file with a lock. When the main app "dies" then the lock will disappear and the other app can detect that and close too.

Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
robert.pope
Posts: 12
Joined: Tue May 02, 2017 6:14 am

CreateThread() vs. CreateVOThread()

Post by robert.pope »

I don't suppose you would be able to advise if the VO functions MemAlloc(), _SizeOf() and MemFree() all operate in static memory / on the stack please (i.e. no dynamic memory)?
User avatar
robert
Posts: 4225
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

CreateThread() vs. CreateVOThread()

Post by robert »

Robert
_SizeOf() is not really a function, so that is safe.
MemAlloc() and MemFree() do not touch the dynamic memory, so they should also be safe.

Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
robert.pope
Posts: 12
Joined: Tue May 02, 2017 6:14 am

CreateThread() vs. CreateVOThread()

Post by robert.pope »

Thanks.

The code I wrote had already shipped in our production release and will only be used briefly for debugging purposes. It won't be left on for too long. :)
Post Reply