Ian Bicking: the old part of his blog

Comment

Hm. Here are the two code snippets with "_" in place of " ". Sorry about that.

In static languages like C, an event-based system offers some complication and hindrance. But in Python, it has been relatively easy for me to write in an event-based manner, because of dynamic function declaration.

Here's a code snippet from my application, Mnet, boiled down to minimal form:

____def request_block_from_peer(self, peerId, blockId):
________def _handle_request_block_result(outcome, failure_reason=None):
____________# No matter what comes out of this reply, we are removing this
____________# blockId/peerId pair from the located-blocks data. (Because either
____________# this result means that we now have the block, or it means that the
____________# peer does not have the block!)
____________self.data.remove_Ids_of_located_blocks(peerId, blockIds=(blockId,))
____________if failure_reason or (outcome['result'] != "success"):
________________# Inform reliability handicapper that this was actually a failure.
________________self.mtm._keeper.get_counterparty_object(peerId).decrement_reliability()
________________# Wake the block wrangling strategy -- failure to retrieve a block is the kind of thing that it might want to respond to.
________________self.strategy.schedule_event()
____________else:
________________self._handle_block(blockId, outcome['data'], peerId)
_
________self.mtm.initiate(peerId, "request block", blockId, outcome_func=_handle_timely_request_block_result)

Back in the bad old days when this project used Python's multithreading, this code would have looked something like this:

____def request_block_from_peer(self, peerId, blockId):
________try:
____________outcome = self.mtm.initiate(peerId, "request block", blockId)
________except TransactionFailure, le:
____________outcome = {'result': "transaction failure", 'le': le}
_
________# No matter what comes out of this reply, we are removing this
________# blockId/peerId pair from the located-blocks data. (Because either
________# this result means that we now have the block, or it means that the
________# peer does not have the block!)
________self.data.remove_Ids_of_located_blocks(peerId, blockIds=(blockId,))
_
________if outcome['result'] != "success":
____________# Inform reliability handicapper that this was actually a failure.
____________self.mtm._keeper.get_counterparty_object(peerId).decrement_reliability()
____________# Wake the block wrangling strategy -- failure to retrieve a block is the kind of thing that it might want to respond to.
____________self.strategy.notify()
____________return None
________else:
____________return (blockId, outcome['data'], peerId,)


Now, the multithreading version might be easier to read because the sequence of runtime events follows the sequence of code, but I hope you will agree that the event-based version is clear enough.

The nice thing about the event-based approach is that I don't have to spend any time worrying about the possibility that data will change out from under me during the execution of the "_handle_request_block_result()" function. That function, and *all* functions, get executed atomically, in their entirety, without any of the data that they depend on changing in any way other than as dictated by the code of that one function. It is difficult to express what a blessed relief it is to be able to just naively read the code and assume that if it says "remove the id and then decrement the reliability" then both of those effects will happen atomically, and it will not make any difference which order you do them in, and the data will not unexpectedly change between the first event and the second.

Anyway, when I started out to write this note I just wanted to emphasize to you that for me, using Python, switching from multithreaded to event-based was relatively painless. I've also had to do the same in some C code recently and it was a lot more hassle in C.

Comment on Concurrency: looking for positive models
by Zooko