Factoring threads & friends

Skip to first unread message

Gordon Henriksen

Jan 1, 2004, 6:23:56 PM1/1/04
to perl6-i...@perl.org
I just thought I would put forward a summary of how .NET factors its
thread-related data structures. It's a successful, performant, lock-free
design, and I think a quite interesting factorization of what parrot
presently presents as "the interpreter." It's the single most successful
threading design I've encountered to date.

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

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

Reply all
Reply to author
0 new messages