20,741
edits
m (→Debugging Threads: something for the Nasal console) |
|||
| Line 168: | Line 168: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Manual locking/synchronization is tedious, fragile and often error-prone, I still have to check what exactly your code is doing, wrapping everything in a single object is definitely helpful, for example run this snippet of code in your Nasal console and see for yourself: | |||
<syntaxhighlight lang="php"> | |||
var DBG_LOCKS = 1; | |||
## | |||
# a simple Lockable class that wraps lock/unlock functionality | |||
# | |||
var Lockable = {}; | |||
Lockable.new = func(name) { | |||
var l = thread.newlock(); | |||
return { parents:[Lockable],LOCK:l,name:name }; | |||
} | |||
Lockable.lock = func thread.lock(me.LOCK); | |||
Lockable.unlock = func thread.unlock(me.LOCK); | |||
## | |||
# also print some stats to the console | |||
# | |||
Lockable.do_atomic = func(what, location="(none)" ) { | |||
DBG_LOCKS and print(location,":Trying to lock:", me.name); | |||
var start=systime(); | |||
me.lock(); | |||
DBG_LOCKS and print(location,":Lock held:", me.name); | |||
DBG_LOCKS and print(location,":Waiting time:", systime() - start); | |||
what(); | |||
me.unlock(); | |||
DBG_LOCKS and print(location,":Lock released:", me.name); | |||
} | |||
## | |||
# extend the Lockable class with counter functionality | |||
var counter = Lockable.new("counter"); | |||
counter.value = 0; | |||
counter.get = func me.value; | |||
counter.inc = func(meta) me.do_atomic( func me.value +=1, meta ); | |||
counter.dec = func(meta) me.do_atomic( func me.value -=1, meta ); | |||
## | |||
# we'll run this as the "task" in our worker threads | |||
var stress_test = func(thread_id) { | |||
for (var i=0;i < 50; i+=1) { | |||
counter.inc( thread_id ); | |||
counter.dec( thread_id ); | |||
} | |||
} | |||
# start a bunch of threads, each incrementing/decrementing the counter concurrently (depending on your hardware and OS) | |||
for(var i=0;i < 180; i+=1) { | |||
func { | |||
var c=i; | |||
thread.newthread(func stress_test("Thread #" ~ c)); | |||
}() | |||
} | |||
</syntaxhighlight> | |||
Similarly, a wrapper to abstract threading and thread pools would make things more intuitive - for example, we don't need to use a "busy-wait" loop for all threads to complete, you can simply use semaphores here, but also to send a worker thread sleeping when no work is available, in general it is more efficient to reuse previously allocated threads than spawn/terminate and re-create new ones all the time. | |||
== GC Stats == | == GC Stats == | ||
At the very least, we should also dump stats for each naPool - so that we know how objects are distributed (scalars, vectors, hashes, ghosts). Afterwards, we should look into exposing a function that dumps stats per namespace, so that we can use an extension function that dumps stats gathered via mark() - i.e. to tell how tell how heavy a certain namespace is. | At the very least, we should also dump stats for each naPool - so that we know how objects are distributed (scalars, vectors, hashes, ghosts). Afterwards, we should look into exposing a function that dumps stats per namespace, so that we can use an extension function that dumps stats gathered via mark() - i.e. to tell how tell how heavy a certain namespace is. | ||