The entities involved are the process, the thread, the "app domain." These entities take separate responsibility for some of the features of the parrot interpreter.
The process is the global-most .NET state, owning threads and app domains, and coordinating events such as GC. This is truly global state, and there's not a whole lot of it.
.NET threads contain little more than a stack and an instruction pointer.
App domains are lightweight protected execution environments. Code is loaded into app domains, and types registered in data structures owned by the app domain. Objects are allocated within an app domain. The majority of the .NET runtime is a property of the app domain. Individual classes and libraries cannot be unloaded in .NET, but entire app domains can be.
Stack frames are obviously within a thread's stack, and thus owned by that thread. But each stack frame also is attached to a particular app domain, and thus knows how to allocate objects, etc. without the overhead of an extra pointer in every object; the app domain is .NET's implicit argument, akin to parrot's interpreter parameter. If this were a relational database, one might say that a stack frame was a many-to-many relationship between threads an app domains. So a thread does not belong to a single app domain: This is not a hierarchical design.
Objects live in one and only one app domain. Since every stack frame also knows its app domain, it follows that all visible objects belong to the app domain of the executing thread. Thus memory pools can be easily identified for memory allocation, and the type registry found, etc. etc. etc.—in general, the runtime can be utilized—without the waste of an extra pointer in the object header.
App domains cannot directly reference each others' objects. Communication between app domains most resembles RPC. Only through proxy objects can one app domain interact with another. The default behavior is that objects passed as arguments to proxy methods will be serialized and deserialized into the target app domain (although they can specify that remote proxies will instead be created in the target app domain). These proxies obviously do need to store a pointer to the peer app domain, but other objects do not, keeping overhead low.
Objects do not have fine-grained locks. If their methods require synchronization, their implementations must request it with a block: lock (this) { /* code goes here */ }. Most of the objects in the libraries are not safe for multithreaded access (although their static methods generally are).
GC affects more than one app domain: It halts all threads. (Certain other events do as well, including unloading an app domain, or mucking with loaded types, or backing out JIT optimizations.) The garbage collector is aware of proxy objects that bridge app domains. Since it operates at the process level, it can collect garbage cycles even across app domains (where normal RPC proxies would cause reference cycles).
The .NET design doesn't meet all of requirements Dan set forth, but it's at least an interesting case study in a successful threading environment for a high-performance virtual machine.
—
Gordon Henriksen [EMAIL PROTECTED]