News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Worker thread and GUI

Started by jdoe, July 10, 2008, 11:07:54 PM

Previous topic - Next topic

jdoe


My question is about Iczelion tutorial #15 and #16.

Does the worker thread not supposed to play directly with the window and use messages instead (WM_APP+X) ? Why calling EnableMenuItem from the thread then ? Same with calling MessageBox from the thread, it seems not recommended IMHO.

And why is it safe to set a global variable when a cancel button is clicked and read it from the thread as an ending method ? Isn't it dangerous that when the window procedure set the variable the thread reads it at the same time ?

I don't know what to think about it.


zooba

Quote from: jdoe on July 10, 2008, 11:07:54 PM
Does the worker thread not supposed to play directly with the window and use messages instead (WM_APP+X) ? Why calling EnableMenuItem from the thread then ? Same with calling MessageBox from the thread, it seems not recommended IMHO.

You're right. This may be working for a number of reasons. Possibly EnableMenuItem uses SendMessage internally (quite a number of these sorts of functions do) which is safe. Alternatively this might be a backwards-compatibility hack. (Personally I think the first reason is more likely.)

Quote from: jdoe on July 10, 2008, 11:07:54 PM
And why is it safe to set a global variable when a cancel button is clicked and read it from the thread as an ending method ? Isn't it dangerous that when the window procedure set the variable the thread reads it at the same time ?

There is no danger in this situation because it is a boolean value. The two threads cannot access the variable at exactly the same time, even on a multi-core CPU, because the CPU synchronises memory access. However, if the value is written one byte at a time there is a chance that the other thread will read the entire value while it has only been half written.

For example:

mov [eax], 12345678h
May occur like this:

; [eax] currently contains 00000000h
mov [eax+3], 12h
; [eax] now contains 12000000h
mov [eax+2], 34h
; [eax] now contains 12340000h
mov [eax+1], 56h
; [eax] now contains 12345600h
mov [eax+0], 78h
; [eax] now contains 12345678h


Since the write may be interrupted at any point in that code snippet, you have to be careful about some writes. (Chances are a machine-width write (ie. DWORD for a 32-bit machine) will be atomic, meaning it all gets written at once and can't be interrupted partway through).

The example is only using a boolean value, where TRUE is 00000001h. So the breakdown above looks like this:
; [eax] currently contains 00000000h (FALSE)
mov [eax+3], 00h
; [eax] now contains 00000000h (FALSE)
mov [eax+2], 00h
; [eax] now contains 00000000h (FALSE)
mov [eax+1], 00h
; [eax] now contains 00000000h (FALSE)
mov [eax+0], 01h
; [eax] now contains 00000001h (TRUE)


In this case, if the write is interrupted by a read, the value appears unchanged. If the write was done in the opposite order (ie. LSB first) then the value would change to TRUE immediately. If the write is atomic (which is probably is) then the value would also change to TRUE immediately. As shown above it takes a few cycles for the value to actually change, but that is largely irrelelvant in this case. If the value is a pointer, using only part of it may crash the entire program.

Basically, the aim is to ensure that threads can only access complete variables, not ones that have been partially modified. Extending this to linked lists and more complex data structures is more difficult: removing an entry from a doubly-linked list requires enough processing that is it likely to be interrupted partway through (eventually). In this case the second thread sees a broken list with dangling pointers.

Hopefully this has cleared some things up. Safe multithreading is hard, but not quite as hard as some make out.

Cheers,

Zooba :U

jdoe

Quote from: zooba on July 11, 2008, 02:17:51 AM

Hopefully this has cleared some things up.



Couldn't be more clear

Thanks

:thumbu


hutch--

JD,

zooba is right, you have a risk if you are writing data that used more than 1 instruction as the implied LOCK mechanism only works one instruction at a time. If you write a pointer to the da6ta in one instruction you are safe but if you need to write the data directly you need a protection echanism, basicaly a critical section as the OS provides so the read or write is not interrupted.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

jdoe

#4
Quote from: hutch-- on July 11, 2008, 04:21:16 AM
JD,

zooba is right, you have a risk if you are writing data that used more than 1 instruction as the implied LOCK mechanism only works one instruction at a time. If you write a pointer to the da6ta in one instruction you are safe but if you need to write the data directly you need a protection echanism, basicaly a critical section as the OS provides so the read or write is not interrupted.


These "Critical Section Objects" were mysterious for me. I knew about InterlockedDecrement and InterlockedIncrement but I didn't had the whole picture in mind. Reading about "Worker Threads" made me think that maybe I've been lucky to not came across a "deadlock" earlier. If someone have an example of "deadlock", I'll be curious to test it. Real life experience is more valuable than reading it.  :bdg

Thanks for your help guys.


zooba

I recommend this 'tutorial'/guide. It is based in .NET (C#) and uses those classes, but the concepts are explained extremely well.

The page on deadlocks is probably what you're looking for here.

Cheers,

Zooba :U