Page 1 of 1

asm multi-thread question

Posted: Sat Mar 07, 2009 10:24 pm
by ProphetOfDoom
Hi,

It's not a broken program, just a question about multi-threading that's been puzzling me all day. I must have made a mistake in my logic somewhere or there's something I don't know about how CPUs/Windows work. Why do integers shared between threads not need a mutex? I'll show you what I mean.



Check out this program:


Code: Select all


Procedure ThreadProc(*dat.Long)

  *dat\l = *dat\l * 5

EndProcedure





Define Thread.l

Define Value.l





Value = 1



Thread = CreateThread(@ThreadProc(),@Value)



Value = Value * 5



WaitThread(Thread)


OpenConsole()



PrintN("Value = " + Str(Value))



Input()





I compiled it with the command line:


Code: Select all


pbcompiler tt.pb /THREAD /COMMENTED



The variable 'Value' initially contains 1. Both threads multiply it by 5, giving a result (as expected) of 25. But a thread can be interrupted at any time, right? So if the new thread was interrupted at the point I've indicated in the asm output, both threads would find 'Value' to contain 1, both would multiply it by 5 and both would replace 5 on the stack... giving the wrong answer.


Code: Select all


; multiplication code from the second thread

; *dat\l = *dat\l * 5

  MOV    ebp,dword [esp+PS0+0]

  MOV    ebx,dword [ebp]

  ; say it switches back to the main thread now

  IMUL   ebx,5

  PUSH   ebx

  MOV    ebp,dword [esp+PS0+4]

  POP    eax

  MOV    dword [ebp],eax



; multiplication code from the main thread

; Value = Value * 5

  MOV    ebx,dword [v_Value]

  IMUL   ebx,5

  MOV    dword [v_Value],ebx   



I've tried running it thousands of times but it always prints 25. How does this work?

Posted: Sat Mar 07, 2009 10:39 pm
by Trond
How does this work?
Because the work the thread has to do is so little, it gets to do all its work before it's interrupted. Don't rely on that.

Posted: Sat Mar 07, 2009 10:43 pm
by ProphetOfDoom
Hi,

Right. Thanks for the reply. But I used the threadsafe compiler option so why did PB output code that isn't threadsafe? Are we supposed to do that ourselves manually? Seems a bit odd...

EDIT: or is it impossible to generate threadsafe code in this instance? If so, what would people recommend? Use one mutex per shared variable?

Posted: Sat Mar 07, 2009 11:35 pm
by Kaeru Gaman
> why did PB output code that isn't threadsafe?

where did it?

sure it IS threadsafe!

you create ONE variable, pass the pointer to a thread,
multiply the value of this valiable in both threads (main and subthread)
and get the correct 25..

so why do you say it isn't threadsafe? o_O

Posted: Sat Mar 07, 2009 11:49 pm
by ProphetOfDoom
Kaeru Gaman wrote:so why do you say it isn't threadsafe? o_O
I already explained why.

Posted: Sat Mar 07, 2009 11:50 pm
by freak
> so why do you say it isn't threadsafe? o_O

Because "it works" <> "its threadsafe". As long as there is potential for a race condition it isn't save. No matter if you can actually observe it or not.

@ProphetOfDoom

The threadsafe switch affects the PB libraries behavior. It doesn't change individual access to global variables because that would kill the performance of the code.

So try to minimize shared variable access between threads and protect those with a mutex, then it will be save. A good way is to have the thread access all global data it needs (mutex protected), then do most of its work with local variables only and finally store the result back to shared data (again protected). This way the mutex access (and wait time) is minimized.

btw, an individual read or write to such a variable (of size < quad) is atomic (read threadsafe). But what you do is basically a "read, modify, write" behavior and that is not atomic and therefore can be interrupted as you realized.

Posted: Sat Mar 07, 2009 11:52 pm
by cxAlex
The Result is correct. I think you mean the Readers-Writers-Problem. If the timing is bad/good the Result could be 5. But that would be incorrect.

//Edit: freak was faster. Race Conditions is the keyword.

Posted: Sat Mar 07, 2009 11:54 pm
by ProphetOfDoom
Thanks very much Freak, it's good to have it confirmed, I *knew* someone would come along and make out i was going mad. And thanks for the tips too. :)

EDIT: thanks Alex too :)

Posted: Sat Mar 07, 2009 11:58 pm
by freak
btw, such a race condition is much easier to observe if you execute the code in question more often:

Code: Select all

Procedure ThreadProc(*dat.Long)
  For i = 1 To 100000
    *dat\l = *dat\l + 1
  Next i
EndProcedure

Define Thread.i
Define Value.l

Value = 1
Thread = CreateThread(@ThreadProc(),@Value)

For i = 1 To 100000
  Value = Value + 1
Next i

WaitThread(Thread)

OpenConsole()
PrintN("Value = " + Str(Value)) ; would be 200000 if this worked
Input() 
Here the correct result is quite rare, especially on multicore systems. If you still get the "correct" result, just increase the counts.

Posted: Sun Mar 08, 2009 12:07 am
by cxAlex
An here is the "safe" Version of freaks Code with the correct Result:

Code: Select all

Structure SafeLong ; Safe Long
  *Long.Long
  Mutex.i
EndStructure

Procedure ThreadProc(*Safe.SafeLong)
  Protected *dat.LONG = *Safe\Long
  For i = 1 To 100000
    LockMutex(*Safe\Mutex) ; Lock Mutex to prevent Readers-Writers Problem
    *dat\l = *dat\l + 1
    UnlockMutex(*Safe\Mutex)
  Next i
EndProcedure

Define Thread.i
Define Value.l
Define SValue.SafeLong

Value = 0
SValue\Long = @Value
SValue\Mutex = CreateMutex()

Thread = CreateThread(@ThreadProc(), @SValue)

For i = 1 To 100000
  LockMutex(SValue\Mutex)
  Value = Value + 1
  UnlockMutex(SValue\Mutex)
Next i

WaitThread(Thread)

FreeMutex(SValue\Mutex)

OpenConsole()
PrintN("Value = " + Str(Value)) ; would be 200000 if this worked
Input()
This is an example how to don't use threads because most oft the time the program is waiting for the mutex an the performance is .... .