There’s one variant of Petri nets, called counted Petri nets, which I’m fond of for personal reasons. As Petri net variants go, it’s a sort of sloppy but simple one, but as I said, I’m fond of it.
As a warning, there’s a bit of a diatribe beneath the fold, as I explain why I know about this obscure, strange Petri net variant.
My first project in grad school involved building a simulator for a network protocol specification language called Estelle. Estelle is, as I like to describe it, a formal specification language with absolutely no useful formal qualities.
The problem with Estelle is that it’s based on something called augmented heirarchical communicating finite state machines. Communicating FSMs are fine; heirarchical FSMs are fine. They’re not my favorite model, but they’re useful, valid techniques for describing things.
Where Estelle got into trouble was the “augmented” part. What that meant is that
each FSM had a bunch of data associated with it, and each transition had a set of guard conditions and effect code written in Pascal. So a state transition could run an arbitrary Pascal program that altered the state of the FSM without changing the visible FSM state; and that change could affect the guard conditions determining what transitions could occur later. So every Estelle transition is basically unlimited in its effects. Any analysis that tried to answer questions about a specification that used this capability generally reduced to the halting problem; but you really needed to use it to represent the state
involved in communication protocols!
Anyway, I got roped into this, and I was supposed to port a huge Smalltalk simulator of Estelle to C. Only I’d never really used Smalltalk. I knew the theory of it, but I’d never had access to a real Smalltalk interpreter. So as a way to learn Smalltalk, and to get into the swing of this protocol specification stuff, I wrote a simple counted Petri net simulator.
Interestingly, I can’t find anything about counted Petri nets on the internet, so I assume they died an ignomious death. I’ll revive them for a brief moment, because they’re an interesting simple example of how to extend Petri nets. The extension is very simple and very minimal, and has some theoretical awkwardness, but it’s useful for expressing certain kinds of fairly common concurrency patterns.
All that you do when you extend to counted Petri nets is to add an integer to each transition. The transition can fire not when all of its input edges have tokens waiting, but when some collection of tokens from the input arcs can provide enough tokens. The counts on the edges become a bound on the maximum number of tokens that can move across the arc.
What this is useful for is things like the worker pool pattern. In this kind of concurrency, you have one main thread, and many workers. The main thread creates tasks which are put into a pool. The workers each grab tasks from the pool and perform the tasks, until no tasks are left in the pool. The reason for using a pool is load balancing: the tasks take different amounts of time to complete, and you don’t know which tasks are going to take longer. So each of the workers grabs a random task from the pool, and runs it. If it’s a fast one, it finishes it quickly, and then grabs another. This means that the processors will all be busy processing tasks for roughly the same amount of time, but they’ll end up processing different numbers of tasks. The synchronization scheme for this is for the main thread to set up the task pool, and then wait after putting the tasks into the pool, the main thread waits until all of
the tasks are complete.
How would you represent a worker pool with a Petri net? You have a pool of workers thread, all of which are basically equivalent. The main thread puts tokens (representing the tasks) into a place representing the task pool. The task pool has a collection of out edges, one for each thread, which carry the task tokens to a subnet representing the task threads. The worker threads each have a place at the
end where they deposit tokens for completed tasks. The main thread waits on a transition which doesn’t get a token until all of the tasks are completed. That token comes from a place which gets its token only when all of the tasks are complete. Since the different tasks will have performed different numbers
of tasks, we use a transition that fires when all of the tasks are complete. A very simple example of this, with two worker tasks is in the image to the right. The main thread is colored green; the places and transitions that control the synchronization of worker threads are red, and the worker threads themselves are blue.
The counted net is a strange sort of hybrid. It’s basically adding a primitive sort of counting to the nets, which is useful. But it does it in a way which is extremely limited. For example, in a real task pool scenario, you wouldn’t know how many tasks were going to be dispatched to the pool; but the transition needs to be marked with a specific value. So for the purposes of the net, you pretend to know.
As I mentioned, this adds a bit of theoretical awkwardness. One of the great things about Petri nets is that they’ve got a very strong notion of synchronization. But with counted nets, that gets weakened. It doesn’t show in this example – but for non-trivial nets, you can get cases where things get tangled – where you want at least 1 token from each of some group of places, but you can’t guarantee that properly
without adding a bunch of extra layers of places and transitions – which messes up the simplicity and clarity which is the main reason for using Petri nets.
Next week, I’ll describe colored Petri nets, which are a much stronger, much cleaner way of extending the capability of Petri nets. The basic idea is that tokens can carry information, and lamba calculus functions can be associated with transitions.