Problem: eine Ressource, die von mehreren Änderungen parallel betroffen ist. (Mit den Fachbegriffen von shared memory und concurrent ressource access bin ich nicht wirklich vertraut, aber das Problem ist klar.) Beispiel sei ein lokaler Cache von Objekten (die eindeutig identifiziert sind), die anhand eben dieser ID geholt werden, geändert werden, da ja in JS objekte immer per Referenz gegeben sind, also automatisch auch im Cache geändert werden. Hier sollte natürlich dasselbe Objekt zu gg. zeit immer nur von genau einem Job geändert werden, was, falls das in einem nicht-parallelen worker script passiert, sicher auch kein problem darstellt. Sobald dieser Zugriff aber von zeitlich unbestimmten Events ausgelöst wird, gibt es das problem des konkurrenten Zugriffs. Soweit ich weiß, ist das bei synchronen Funktionen (also wenn über den gesamten ändernden Zugriff hinweg keine asynchronen callbacks stattfinden) kein problem, da die interne event queue die Events konsequent nacheinander abarbeitet, aber sobald der Zugriff mit vielen asynchronen Operationen geschieht, ist es doch so, dass Callbacks auch auf tieferer Ebene in die event queue gelangen, und so eben mehrere parallele Ops mit Kontextwechsel quasi-parallel ausgeführt werden und so natürlich das geteilte Objekt in falsche Ausgangszustände gerät. Hier muss explizit das Objekt gelockt werden, indem alle Jobs für eine Dateneinheit in eine queue eingereiht werden (die natürlich genauso global wie die Daten sein muss).
Created
November 11, 2011 21:15
-
-
Save akidee/1359300 to your computer and use it in GitHub Desktop.
Concurrent ressource access
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### | |
Ein Beispiel, und es funktioniert: | |
### | |
# global object collection, by ID | |
objById = { | |
id1: {} | |
id2: {} | |
id3: {} | |
} | |
# job that changes 1 object and optionally returns a result (async) | |
# Result must be that prop includes a list in which after every 1, there must be a 2, and after every 2, there must be a 1. | |
# Wrong parallel calling can result to several 1s or 2s after another | |
changeObj = (obj, __) -> | |
if !obj.prop | |
obj.prop = [] | |
obj.prop.push(1) | |
setTimeout(-> | |
obj.prop.push(2) | |
__(null, obj.prop) | |
, 100) | |
queues = ((queues = {}) -> | |
# global job list | |
jobsById = { | |
} | |
# push a job for an ID | |
push = queues.push = (id, job, args...) -> | |
jobs = jobsById[id] | |
exist = jobs && jobs.length | |
if !exist | |
jobs = jobsById[id] = [] | |
# wrap callback, end of job triggers next in queue | |
# The last arg is always the callback | |
callback = args.pop() | |
args.push( | |
-> | |
callback.apply(null, arguments) | |
jobs.shift() | |
shift(id) | |
) | |
jobs.push([ job, args ]) | |
# Queue execution is started because queue was clear | |
if !exist | |
shift(id) | |
# Exec the next job for a key | |
shift = queues.shift = (id) -> | |
jobs = jobsById[id] | |
if !jobs || !jobs.length | |
return | |
job = jobs[jobs.length - 1] | |
job[0].apply(null, job[1]) | |
queues | |
)() | |
### | |
# How not to do it | |
changeObj(objById.id1, (e, r) -> | |
console.log '1. call: ', r | |
) | |
changeObj(objById.id1, (e, r) -> | |
console.log '2. call: ', r | |
) | |
### | |
# How to do it | |
queues.push('id1', changeObj, objById.id1, (e, r) -> | |
console.log '1. call: ', r | |
) | |
queues.push('id1', changeObj, objById.id1, (e, r) -> | |
console.log '2. call: ', r | |
) | |
### | |
Hier sind nun alle Objekte innerhalb eines Prozesses global. Für shared memory zwischen mehreren isolierten prozessen ist da natürlich eine Datenbank und ein globaler Lock (zB Redis mit blockenden Listen) nötig. | |
### |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment