The Routines in sclang are actually coroutines. This is actually made clear in some classlib comments…
Object {
// coroutine support
yield {
_RoutineYield
^this.primitiveFailed
}
}
…but in less comp-sci terms in the SC help files.
(Edit: actually the Routine help says, somewhat confusingly
Routines can be used to implement co-routines as found in Scheme and some other languages.
But I really don’t see how SC Routines differ from actual coroutines, so that they would have to be “used to implement” those.)
So as mentioned above the only context switches happen when you explicitly yield (and variations, yieldAndReset, alwaysYield) and when when you call next (and less often synonyms like value/run/resume directly) on a Routine. Those are pretty much the only times when switchToThread is called in the underlying C++ implementation.
Of course those calls can be nested in some other function calls (e.g. embedInStream), so it’s not always obvious where that context-switching happens, but for the most part it is.
(Aside: it’s basically impossible to port the classlib “as is” to a language without coroutine support. See e.g. paper on ScalaCollider .)
Also Routines/Threads have their own Environment, which is actually a little difficult to change from “the outside”, i.e. from another scland Thread. (Stack frames cannot be changed with “hacks” from within SC lang as fas as I know; they can only be inspected by creating copies, e.g. DebugFrame created by getBackTrace.) So only the heap-stored objects are usually “at risk” of being changed from another Thread/Routine, i.e. the local Thread vars and and environment (usually) will be pointing to the same objects after a context switch, but the “contents” of these objects may have changed. But then that’s not really a different problem from when you e.g. call library function that executes on the same Thread as the caller but changes some heap-stored objects.