Mike I don't think that you can rely on the threadsafety of these functions. Even if they are threadsafe in C Python (which I doubt that 'set' is), the locking in Jython in more fine grained and would likely catch you out.
I would suggest that you should routinely wrap shared datamodels like these in thread locks to be certain about things. I would also suggest that a small change to the Value class would make it possible for client code to subclass it, which might make it more flexible. Here is my suggestion, bare in mind that I have not tested the thread locking code beyond making sure that it runs :-) Regards Richard #!/usr/bin/python from threading import Lock, RLock class Locker(object): def __init__(self,lock): self._lock = lock def __call__(self): lock = self._lock def wrap(f): def newFunction(*args, **kw): lock.acquire() try: return f(*args, **kw) finally: lock.release() return newFunction return wrap write_lock = Locker(Lock()) read_lock = Locker(RLock()) class ConcurrentUpdate(Exception): pass class Value(object): def __init__(self, version, value,store,key, *tup, **kw): self.version = version self.value = value self.store = store self.key = key self.tup = tup self.kw = kw def __repr__(self): return "Value"+repr((self.version,self.value)) def set(self, value): self.value = value def commit(self): self.store.set(self.key, self) def clone(self): return self.__class__(self.version, self.value,self.store,self.key,*self.tup, **self.kw) class Store(object): def __init__(self, value_class=Value): self.store = {} self.value_class = value_class @read_lock() def get(self, key): return self.store[key].clone() @write_lock() def set(self, key, value): if not (self.store[key].version > value.version): self.store[key] = self.value_class(value.version+1, value.value, self, key) value.version= value.version+1 else: raise ConcurrentUpdate @read_lock() def using(self, key): try: return self.get(key) except KeyError: self.store[key] = self.value_class(0, None,self,key) return self.get(key) def dump(self): for k in self.store: print k, ":", self.store[k] S = Store() greeting = S.using("hello") print repr(greeting.value) greeting.set("Hello World") greeting.commit() print greeting S.dump() # ------------------------------------------------------ par = S.using("hello") par.set("Woo") par.commit() # ------------------------------------------------------ print greeting S.dump() # ------------------------------------------------------ greeting.set("Woo") try: greeting.commit() except ConcurrentUpdate: print "Received ConcurrentUpdate exception" print repr(greeting), repr(greeting.value) S.dump() On Saturday 08 December 2007, Michael Sparks wrote: > I've just posted this message on comp.lang.python fishing for comments, but > I'll post it here in case there's any feedback this way :-) > > (apologies for any dupes people get - I don't *think* python-uk & pyconuk are > strict subsets) > > I'm interested in writing a simple, minimalistic, non persistent (at this > stage) software transactional memory (STM) module. The idea being it should > be possible to write such a beast in a way that can be made threadsafe fair > easily. > > For those who don't know, STM is a really fancy way of saying variables > with version control (as far as I can tell :-) designed to enable threadsafe > shared data. > > I'm starting with the caveat here that the following code is almost > certainly not threadsafe (not put any real thought into that as yet), > and I'm interested in any feedback on the following: > > * Does the API look simple enough? > * Are there any glaring mistakes in the code ? (It's always harder to see > your own bugs) > * What key areas appear least threadsafe, and any general suggestions > around that. > > If I get no feedback I hope this is of interest. Since these things get > archived, if you're reading this a month, 6 months, a year or more from > now, I'll still be interested in feedback... > > OK, API. > > First of all we need to initialise the store: > > S = Store() > > We then want to get a value from the store such that we can use the value, > and do stuff with it: > > greeting = S.using("hello") > > Access the value: > > print repr(greeting.value) > > Update the value: > > greeting.set("Hello World") > > Commit the value back to the store: > > greeting.commit() > > If you have concurrent updates of the same value, the following exception > gets thrown: > ConcurrentUpdate > > cf: > >>> S = Store() > >>> greeting = S.using("hello") > >>> par = S.using("hello") > >>> greeting.set("Hello World") > >>> par.set("Woo") > >>> greeting.commit() > >>> par.commit() > Traceback (most recent call last): > File "<stdin>", line 1, in <module> > File "<stdin>", line 12, in commit > File "<stdin>", line 11, in set > __main__.ConcurrentUpdate > > That's pretty much the simplest API I can come up with. (I've tried a few > others but didn't really like them) > > The way this works is we have a Store that manages Values. (perhaps a better > name may be variables to avoid clashing with pythons parlance of labels and > values?) > > Anyhow, you can ask the store for Value, which it will give you. The Value > knows what it's called and where it's from, so when you tell it to commit, > it can try to do so. The little detail here is you get a copy of the > Value not the stored Value. (This is to avoid accidental concurrent update > of the same actual object) > > As a result, Store looks like this: > > class Store(object): > def __init__(self): > self.store = {} > > def get(self, key): > return self.store[key].clone() > > def set(self, key, value): > if not (self.store[key].version > value.version): > self.store[key] = Value(value.version+1, value.value, self, key) > value.version= value.version+1 > else: > raise ConcurrentUpdate > > def using(self, key): > try: > return self.get(key) > except KeyError: > self.store[key] = Value(0, None,self,key) > return self.get(key) > > def dump(self): > for k in self.store: > print k, ":", self.store[k] > > You'll note that the set method is the greatest candidate for any possible > race hazard here - though I can see a possible boundary issue in "using". > (I think :-) > > Otherwise I think the above code is relatively straightforward. You'll note > that this API allows this: > > greeting.set("Hello") > greeting.commit() > greeting.set("Hello World") > greeting.commit() > greeting.set("Hello World. Game") > greeting.commit() > greeting.set("Hello World. Game Over") > greeting.commit() > > The other class is value that looks like this: > > class Value(object): > def __init__(self, version, value,store,key): > self.version = version > self.value = value > self.store = store > self.key = key > > def __repr__(self): > return "Value"+repr((self.version,self.value,self.store,self.key)) > > def set(self, value): > self.value = value > > def commit(self): > self.store.set(self.key, self) > > def clone(self): > return Value(self.version, self.value,self.store,self.key) > > To me this looks like a pretty complete minimalistic thing, which does seem > to work OK, but I'm interested in the three points I mention above - if > anyone is willing to comment - specifcally: > > * Does the API look simple enough? > * Are there any glaring mistakes in the code ? (It's always harder to see > your own bugs) > * What key areas appear least threadsafe, and any general suggestions > around that. > > Full code below. > > Many thanks for any comments in advance, > > > Michael > -- > Michael Sparks, Kamaelia Project Lead > http://kamaelia.sourceforge.net/Developers/ > http://yeoldeclue.com/blog > > #!/usr/bin/python > > class ConcurrentUpdate(Exception): pass > > class Value(object): > def __init__(self, version, value,store,key): > self.version = version > self.value = value > self.store = store > self.key = key > > def __repr__(self): > return "Value"+repr((self.version,self.value)) > > def set(self, value): > self.value = value > > def commit(self): > self.store.set(self.key, self) > > def clone(self): > return Value(self.version, self.value,self.store,self.key) > > class Store(object): > def __init__(self): > self.store = {} > > def get(self, key): > return self.store[key].clone() > > def set(self, key, value): > if not (self.store[key].version > value.version): > self.store[key] = Value(value.version+1, value.value, self, key) > value.version= value.version+1 > else: > raise ConcurrentUpdate > > def using(self, key): > try: > return self.get(key) > except KeyError: > self.store[key] = Value(0, None,self,key) > return self.get(key) > > def dump(self): > for k in self.store: > print k, ":", self.store[k] > > S = Store() > greeting = S.using("hello") > print repr(greeting.value) > greeting.set("Hello World") > greeting.commit() > # ------------------------------------------------------ > print greeting > S.dump() > # ------------------------------------------------------ > par = S.using("hello") > par.set("Woo") > par.commit() > # ------------------------------------------------------ > print greeting > S.dump() > # ------------------------------------------------------ > greeting.set("Woo") > greeting.commit() > > print repr(greeting), repr(greeting.value) > > S.dump() > _______________________________________________ > pyconuk mailing list > [EMAIL PROTECTED] > http://mail.python.org/mailman/listinfo/pyconuk > > -- QinetiQ B009 Woodward Building St. Andrews Road Malvern Worcs WR14 3PS Jabber: [EMAIL PROTECTED] PGPKey: http://pgp.mit.edu:11371/pks/lookup?op=get&search=0xA7DA9FD9 Key fingerprint = D051 A121 E7C3 485F 3C0E 1593 ED9E D868 A7DA 9FD9
signature.asc
Description: This is a digitally signed message part.
_______________________________________________ python-uk mailing list python-uk@python.org http://mail.python.org/mailman/listinfo/python-uk