Q: How big should active objects get?
A: As big as possible, but not bigger.
AOs as Strategic Components
Active Objects (AOs) are strategic components, and using them for small, tactical tasks is counterproductive. As I’ve stated several times in my videos, AOs can incorporate far more functionality than traditional blocking threads precisely because they don’t block and remain responsive to all events.
How Many Is Many?
My original QP framework design permitted a maximum of 8 to 16 AOs. To me, this was plenty. But after a lot of arm-twisting by customers, I had to increase the limit to 64 AOs. Some customers have pushed hard even against this (“My RTOS XYZ supports 256 threads. How much does your QP support?”), but 64 is where I drew the line in the sand. This limit can be lowered in QP ports, and for 32-bit machines, the optimal limit is 32 AOs.
AOs as Priority Levels
The most important property of an AO is its relative priority in the system. Everything that can execute at this priority level goes into that AO.
AOs as Resource Owners
The second consideration is resource ownership and encapsulation, with the goal of no direct sharing of anything. Instead of direct sharing, the rest of the system exchanges events with the AO that owns and encapsulates any given resource.
Common Misunderstandings
These guidelines are often misunderstood and twisted in two ways. First, people (mis)understand that this restricts them to having only 64 (or 32) state machines in the whole system. And second, people (mis)understand that any given AO must encapsulate only one resource. Both myths are not true.
Regarding the number of state machines, an AO can contain many passive but stateful “Orthogonal Components,” such as “communication channels,” ADC channels, and other stateful components.
Regarding resource ownership, a given AO can easily manage many of them. The considerations are only about the relative priority at which these resources need to be serviced and the cohesion of functionality. You want high cohesion inside an AO and low coupling among the different AOs.
Proliferation of Concurrency
Our human brains don’t deal well with concurrency, and introducing more of it does not help. If running certain functionality in one thread (priority level) is possible, put it all in just one thread (one AO). Again, AOs are superior to traditional threads because you can put more functionality in an AO than a traditional blocking thread. The only reason to split functionality (and introduce another AO) is that some things have hard real-time constraints that require executing before others. That’s what priorities are for, and that’s when you need another AO.
Careful with "Divide and Conquer"
Many developers use threads/AOs primarily as a “divide and conquer” strategy, mapping threads/AOs to a “block diagram” reflecting the hardware modules or some arbitrary decomposition they invented. The early stages of software design typically identify too many “components,” which is why developers always ask for more threads. However, not all these “components” need to be concurrent AOs/threads. As explained before, many can be combined at a given priority level.
Problems with Events
Event-driven programming has many good properties (that’s why I advocate it in the first place). Still, the biggest downside is that the interactions based on events are mainly hidden and challenging to follow. You just don’t easily see the “connections” among various event-driven components (e.g., AOs) based only on the elusive event exchanges. Too many haphazardly exchanged events, and there is complete and intractable chaos.
Everything in Moderation
In the end, I recommend moderation and common sense. If it doesn’t help, don’t do it, and do what feels right.