HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

I need a mutex

01-26-2004, 11:10 PM#1
Extrarius
I'm having some problems with threading in WC3. I have two triggers that change a global variable (increments it by 1), and once in a while they both run at the same time and the variable ends up only going up by 1 instead of one for each trigger. There are also other places in my code where it could be a problem but I haven't seen it yet.

I'm trying to figure out some way to make a mutex(1). There isn't any way to make code atomic AFAIK(if there was it would solve my problem), but I was thinking that maybe the GetTriggerExecCount could be used to aleviate this problem somehow, but I'm not quite sure how yet. Any help on this would be greatly appreciated.

(1) For those that don't know what a mutex is, its just a way of preventing two pieces of code that access a single resouce from running at the same time. Normally on a single processor system it couldn't happen, but when a thread (the code in a trigger in this case) is interrupted while accessing that resource and another thread gets to run that access that same resource, problems occur.
01-26-2004, 11:43 PM#2
Vidstige
Something like this might work...
Code:
// thread 0
loop
    exitwhen a == 0
    call Sleep(?)
endloop

// Do important stuff

set a = 1
Code:
//thread 1
loop
    exitwhen a == 1
    call Sleep(?)
endloop

// Do important stuff

set a = 0

But I guess that this really isn't the solution of the right problem...
01-26-2004, 11:45 PM#3
Extrarius
I don't think that works in this situation since each thread can be instantiated multiple times and even if trigger A is running 50 times 'at once', a certain part needs mutex access to the global variables.
01-26-2004, 11:55 PM#4
Vidstige
*mumbles* Maybe I should try to find my book in real-time programming... emote_confused
01-27-2004, 04:29 AM#5
AIAndy
What Vidstige wrote will be as good a mutex as you can get in JASS (although I'd use a boolean). Usually 2 threads do not run at the same time anyway unless you have very long threads that are not stopped by a sleep.

Maybe you should post us what the triggers you want the mutex for are.
01-27-2004, 04:56 AM#6
Extrarius
A simplifcation of the problem is below (though its just where I originally encountered the problem, because of it I'm afraid it will happen with other triggers that use globals as well):
Code:
function AsyncFuncCall takes code callback returns nothing
    local trigger trg = CreateTrigger()
    call TriggerAddAction(trg, callback)
    call TriggerExecute(trg)
    call TriggerSleepAction(0.0)
    call DestroyTrigger(trg)
endfunction

function Something takes nothing returns nothing
    set udg_ThreadsRunning = udg_ThreadsRunning + 1
    loop
        //...
        call TriggerSleepAction(0)
        exitwhen udg_EndThreads
    endloop
    set udg_ThreadsRunning = udg_ThreadsRunning - 1
endfunction

function OtherThing takes nothing returns nothing
    set udg_ThreadsRunning = udg_ThreadsRunning + 1
    loop
        //...
        call TriggerSleepAction(0)
        exitwhen udg_EndThreads
    endloop
    set udg_ThreadsRunning = udg_ThreadsRunning - 1
endfunction

function ResetGame takes nothing returns nothing
    set udg_EndThreads = true
    loop
        exitwhen udg_ThreadsRunning == 0
        call TriggerSleepAction(0)
    endloop
    set udg_EndThreads = false
    call AsyncFuncCall(function Something)
    call AsyncFuncCall(function OtherThing)
    call TriggerSleepAction(0)
endfunction
There is a chance that after calling ResetGame, ThreadsRunning will be 1 instead of 2 in multiplayer (never had it happen in SP). Also, after the first call of ResetGame, there is a chance that later calls of it will never return because sometimes the increments will both be counted (making ThreadsRunning 2) but only one of the decrements will be counted(so the loop exitwhen is never met). I've never had it happen in normal triggers, but I'm 99% sure it can happen and I'd like to prevent that as best as possible.
01-27-2004, 01:25 PM#7
AIAndy
Hmm, you could try a double or triple semaphore.

Like this:
function GetLock takes nothing returns nothing
loop
exitwhen a
call TriggerSleepAction(0)
endloop
set a = false
loop
exitwhen b
call TriggerSleepAction(0)
endloop
set b = false
endfunction

function ReleaseLock takes nothing returns nothing
set a = true
set b = true
endfunction

Of course that will not give you 100% protection.
Because of that you should consider making a fake thread system similar to the job system in AMAI. That way you have a lot more control on when your function is interrupted in the context of the functions in the thread system.
01-27-2004, 01:43 PM#8
Extrarius
Well, that(the code I posted) isn't really the problem so to speak, its just what made me aware of the problem in wc3. What I want to do in my current map that I thing might have problems is to maintain an array of active units. Each time a new unit is created, it will be put in the array (and other information will be put in the same index in other arrays). Each time a unit dies, the info for the last unit in the array will be copied over the dead unit's info and the index of the last unit decremented. I'm afraid that a unit will die, then a new unit will be created and put in the last spot, then the last index will be decremented so the newly created unit is ignored.
01-27-2004, 01:59 PM#9
AIAndy
Well, one solution would be to use the gamecache to store the unit information. That solves the consistency problem.
Another solution would be to put a Sleep before every writing access to the array. That way it is very unlikely that an event will interfere in a way that causes an inconsistency.
01-27-2004, 04:51 PM#10
Extrarius
How would storing it in the game cache fix any inconsistency?
Also, putting a sleep before each manipulation isn't possible because the code that removes dead units also performs other updates, and it is run every 0.1 seconds (using a timer event). A sleep almost always lasts longer than 0.1 seconds, even if you sleep for 0.01 or 0.

I think I figured out a way to do what I want but I it only makes the problem unlikely to ever happen. Basically, I'll have the arrays like now, but instead of looping through it and using the first X indexes, I'll use a random index for each unit and keep a unit group of active units and use their custom value to store their index.
01-27-2004, 05:18 PM#11
Cubasis
hmm?

erhm?, I have always heard that a Jass script is only single-threaded (while it overall has 6 threads i heard, where 5 of them are for AI, and 1 for custom-script)....that is, that at any time, only one thread/trigger/function is in run-time at any time.
And i have done some tons of work with this in mind and it hasn't prooven itself wrong yet, as i've played with having multiple triggers on same matter, and etc etc, timer and stuff like that. In fact, it works like a charm to use global variables to pass information to other triggers. And before i started doing that, i did extensive testing on interuptions of this transfer.

So i can't see what the trouble is, unless you're working with a global in many places with waits and stuff, but then you should either try to use local variables, or thread-variables.

Cubasis
01-27-2004, 05:20 PM#12
Extrarius
It might only run one thread at a time, but it can switch which thread is running at any time which is the real problem.
In the above example, it would calculate 'udg_ThreadsRunning - 1', switch to a thread that modifies ThreadsRunning, and then go back to the original thread and set ThreadsRunning to the value that is now incorrect.

Also, it is not possible to implement what I want without global variables since you can't pass information to a 'thread'(trigger) or pass information from thread to thread any other way.
01-27-2004, 06:11 PM#13
Cubasis
Ehrm, wtf,

Now i think you're confused.....
As that's BS to my knowledge. Runtime only jumps to another thread IF....It finishes the currently running thread....or encounters a TriggerSleepAction, that's exactly what triggersleepaction does, it puts that thread to wait, and checks if some other threads are waiting for run-time. Then everytime it finishes a thread, it checks if some thread has finished sleeping, and goes back to it. But that only happens when it encounters sleeps or finishes a thread (completely).

Cubasis

ps, there is also the chance of the thread timing out, that is, it runs too many statements without sleeping, and then runtime will kill the thread and go to another thread....But i doubt you are having problems with that limit.
01-27-2004, 06:19 PM#14
Extrarius
The example above demonstrates the error. It actually happened on one of my maps in a situation almost exactly like that, and the functions were running to the very end. Not sure how I could be confused.
01-27-2004, 06:59 PM#15
Cubasis
I'm not really understanding what you mean there,

But i guess you're talking about the statement limit getting in your way. Ok, fair enough, i guess. But i just went on and did a little test.

The following code, raised the integer there too cirka 23000 (same every time tried).
Code:
loop
   exitwhen 3 == 4
   set udg_Bla = udg_Bla + 1
endloop

This means that it looped 23000 times before the runtime cut the thread. That seems like a low number, but it's a AWEFULL lot of statements, can you imagine writing all that, ofcourse heavy algorithms help getting up there, but....whatever

And if you are worried about this limit, then it's so very simple/easy to dodge it.... just by placing one TriggerSleepAction(?) somewhere... That resets the statement-count. F.ex. i could get that integer up too 500000 by waiting it every 10000 loops.

Anyhow...

Cubasis