Being able to add an independent, event driven program to any object in the world makes scripting in Second Life really intuitive: you add a script and your object becomes an independent agent able to sense its world through event handlers, sit and think and then effect the world through library calls. It's a model that works equally well whether you're coding a fish which swims around looking for food or an information post that just hands out notecards whenever it's poked.
It's an approach that quickly leads to lots of running scripts. Some of the more complex regions constantly run 1000s of scripts. Abbotts Aerodrome runs around 2000 scripts and while most of these are of the give notecard when poked variety they could just as well be calculating PI or running SETI at Home, which means they all need to behave as if they were independent processes running concurrently within the simulator.
Supporting such high levels of concurrency has been one of the major challenges in integrating Mono in to Second Life. Although Mono and the CLI support the concept of independent threads of execution, each Mono thread maps on to an operating system thread which tend to be fairly heavyweight. By default most operating systems reserve megabytes of memory for each thread and don't cope with more than a few dozen running concurrently. Even reducing the allocated stack size to 32K and using the latest thread libraries on Linux doesn't get you much past 1000 running threads before the system starts to wheeze.
The other difficulty with Second Life scripts is that they can migrate between simulators while they are running. You are perfectly entitled to write a script which never stops running and then to tie it to a rocket that you fire in to another region. This is relatively easy to do when all of your script state is in a single 16K block, but much harder when your script has been Just In Time compiled to machine code and its state is scattered throughout memory, registers and an operating system thread.
The solution we implemented for Second Life was to use RAIL on top of .NETs Reflection and Reflection.Emit facilities to inject microthreading in to the script assemblies, an approach used by the JavaGoX and Brakes projects to implement mobile agents in Java. Our microthread injector searches through the script assembly finding points where the script should yield and inducing the types on the stack at those points. It can then inject extra opcodes in to the assembly which copy the stack in to a heap object and cause the script to yield. Whenever a script yields we can restore another script from its previously saved heap object and start it running again, allowing us to schedule many microthreaded scripts on a single operating system thread. By marking the heap objects as serializable we can then just use the standard .NET communication facilities to migrate scripts to remote simulators where they can continue running.
The approach is memory efficient as the memory needed to save the microthread is allocated when it yields, so a large amount of stack space doesn't need to be conservatively reserved up front for the thread. The problems with this approach is that it is expensive in CPU time spent switching microthreads and the space taken up by injected opcodes.
An alternative approach to microthreading Mono has been taken by Tomi Valkeinen who has written some very funky code (inspired by Eve Online's use of Stackless Python) to save and restore the native stack to switch between microthreads. This approach should be a lot faster than opcode injection. We can't currently use Tomi's microthreads for Second Life as the saved stack may contain pointers which can't be transferred to a remote machine, but it may be possible in the future to use Mono's garbage collector to identify the pointers in the native stack and twizzle them in to a form that can be sent to a remote machine.
It would be very cool to build a library which automatically switched between Tomi's microthreading implementation on Mono and Linden Lab microthreading on .NET. People could then build portable microthreaded applications which take advantage of Mono's native microthreading when it's available.