Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

CreateThread vs. CreateVOThread

114 views
Skip to first unread message

Robert Pope

unread,
Aug 3, 2021, 6:56:40 AM8/3/21
to
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.

Jamal

unread,
Aug 3, 2021, 12:05:19 PM8/3/21
to
According to the docs the CreateThread() and CreateVOThread() are the same:
However, "CreateVOThread() also performs some additional initializations
in the kernel runtime."

But it does not say what those initializations! But I guess it has to do
with the GC suspension during its processing.

Additionally, the docs say (Just in case you did not see it and for others):

CreateVOThread() and ExitVOThread()

"First of all, threads in a Visual Objects application should not be
created by calling the Windows API function CreateThread() directly, but
by calling the Visual Objects runtime function CreateVOThread(). This
function is called exactly like CreateThread() and actually it calls
CreateThread() internally. CreateVOThread() also performs some
additional initializations in the kernel runtime.

Similar to CreateVOThread() there is an ExitVOThread() function that
should be called instead of ExitThread(). However, try to avoid exiting
a thread in a Visual Objects application by calling ExitThread() at all.
Instead, exit your threads by returning from the thread entry point
function."

And the docs go on further:

"When multiple threads are running, each dynamic memory pointer becomes
a shared resource. One thread might load a pointer to a string into a
register. (The GC can only update memory locations, not registers.)
Before actually performing an operation with that pointer, execution is
transferred to another thread that triggers the GC. Now the string
referred to by the register in the first thread is moved, and when
execution returns to the first thread, the pointer in the register is
not valid anymore."



"The problem described above will be resolved in a future release of
Visual Objects, but right now the programmer has to be aware of it."

(I don't think this was ever resolved, and I guess will never be)


"There are two ways of avoiding problems with threads and dynamic data.
The first solution is to disable garbage collection when more than one
thread is active and enabling it only again after all threads but one
finish. This can be done through the Visual Objects runtime functions
DynLock() and DynUnlock(). The disadvantage of this approach is that no
more memory will be freed or reused while the DynLock() is active.
Depending on the application logic, memory consumption might increase
rapidly. Even when the collector runs again, the dynamic memory pool
will stay at the maximum size for future reuse. You might reduce the
size of the dynamic memory pool manually by calling DynShrink().

Disabling the collector globally while a thread is running, is a good
solution when threads only run for a short while and do not generate a
lot of dynamic data garbage. Indexing a database produces a lot of
dynamic data garbage, so that indexing in a thread is not recommended
for bigger databases.

To avoid the collector problem, the solution is not to use dynamic data
at all inside a thread. Another solution is to minimize the use of
dynamic data to very few places, so that DynLock() and DynUnlock() can
be used locally. The Web server that will be presented later is a good
example for this technique."

Robert Pope

unread,
Aug 3, 2021, 1:07:25 PM8/3/21
to
Thanks for replying.

I have seen that documentation / white paper.

In my case locking dynamic memory isn't an option. The thread continues for the lifetime of the application, and this isn't a small application. So yeah, as per my original post, my approach is to avoid using dynamic memory at all in the thread.

What I really hope is that the garbage collector would only ever be called from the main thread. I don't want the worker thread to start the GC. Hence why I'm hoping that calling the Windows API's CreateThread() over VO's CreateVOThread() might be more ideal in that regard. If VO doesn't know my second thread exists, hopefully the garbage collection would never initiate on it. Since the thread is using no dynamic memory it would be unaffected by the GC running on another (the main) thread concurrently.

I guess I'm hoping that someone with internal knowledge of VO threading might still be lurking around these parts to provide some undocumented knowledge. :)

Cheers.

Jamal

unread,
Aug 3, 2021, 6:02:07 PM8/3/21
to
You can post to the X# forum and Robert may know.

dlzc

unread,
Aug 4, 2021, 10:32:11 AM8/4/21
to
Dear Robert Pope:

On Tuesday, August 3, 2021 at 10:07:25 AM UTC-7, Robert Pope wrote:
...
> If VO doesn't know my second thread exists, hopefully the garbage
> collection would never initiate on it. Since the thread is using no
> dynamic memory it would be unaffected by the GC running on
> another (the main) thread concurrently.

Did you write the "second" thread in VO? Does it access VO-defined data / structures / open DBFs? Are all of its memory accesses to global variables? Does it call VO or user functions? If any of these answers is "yes", I think you are going to be in trouble. Consider that the code could be triggered to run while GC is running in the "foreground".

David A. Smith (not a VO-er)

Wolfgang Riedmann

unread,
Aug 4, 2021, 11:07:12 AM8/4/21
to
Hi Robert,

please be aware that you cannot use any VO specific datatype in the
thread like strings, floats or objects because they are collected when
not used.

I would strongly recommend to no use threads in a VO application, and
use rather a second application instead of a second thread linked by
some collaboration mechanism.

Or you may have another alternative: use X# instead. In X# not only the
garbage collector is thread safe, but also the RDDs if you need them.

Wolfgang
--

Robert Pope

unread,
Aug 4, 2021, 1:18:07 PM8/4/21
to
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!

Wolfgang Riedmann

unread,
Aug 5, 2021, 5:47:09 AM8/5/21
to
Hi Robert,

> 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.

this code should be safe, but nevertheless I would use CreateVOThread.

But I think that the GC would never kick in here as you have no local
variables or other things that could make him kick in.

But as Jamal wrote: the only person you can give a correct answer is
Robert v.d.Hulst, and you can reach him through the X# forum.
I would do that as fast as possible as it is holiday time and he may be
absent the next weeks for holiday.

Wolfgang


0 new messages