asm multi-thread question

Everything else that doesn't fall into one of the other PB categories.
ProphetOfDoom
User
User
Posts: 84
Joined: Mon Jun 30, 2008 4:36 pm
Location: UK

asm multi-thread question

Post 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?
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post 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.
ProphetOfDoom
User
User
Posts: 84
Joined: Mon Jun 30, 2008 4:36 pm
Location: UK

Post 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?
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post 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
oh... and have a nice day.
ProphetOfDoom
User
User
Posts: 84
Joined: Mon Jun 30, 2008 4:36 pm
Location: UK

Post by ProphetOfDoom »

Kaeru Gaman wrote:so why do you say it isn't threadsafe? o_O
I already explained why.
freak
PureBasic Team
PureBasic Team
Posts: 5962
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Post 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.
quidquid Latine dictum sit altum videtur
cxAlex
User
User
Posts: 88
Joined: Fri Oct 24, 2008 11:29 pm
Location: Austria
Contact:

Post 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.
ProphetOfDoom
User
User
Posts: 84
Joined: Mon Jun 30, 2008 4:36 pm
Location: UK

Post 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 :)
freak
PureBasic Team
PureBasic Team
Posts: 5962
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Post 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.
quidquid Latine dictum sit altum videtur
cxAlex
User
User
Posts: 88
Joined: Fri Oct 24, 2008 11:29 pm
Location: Austria
Contact:

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