To be precise, fibers in and of themselves aren't exacty what I want, but are the best means to getting it that I've seen so far. They enable efficient implementation of coroutines, which many languages either cannot express at all, or can only express very poorly by using the sledgehammer of a full-on kernel thread to get it. A call stack is a very useful way to group logic, and being forced to go outside the language and ask the OS for another one is a shame.

Game logic is an area where this technique REALLY shines. Case in point: Unity3D. The entire engine is built around C#'s iterator method facility to implement coroutines to control entity logic. Hundreds or even thousands of them can be active with very little overhead compared to a full thread context switch. Unfortunately, they're hamstrung by not being a true coroutine (you can only yield from the top frame).

Right, exactly the above! Fibers are totally uninteresting as a "lighter thread" or unit of scheduling for the reasons that you note, but coroutines are just a better way to write a lot of code. This is basically the entire premise for the Go programming language, so it's worth taking a peek at that if you haven't already.

In my example, it's effectively to yield and restore connection-specific state for different clients. In the equivalent C code, it's a gigantic mess of a state machine specified by big switch statements and is almost impossible to follow the intended flow. With coroutines it's quite simple: you write the code in the most natural way - as if your sockets were blocking - and when you would otherwise block, you yield the fiber instead and come back to it when more socket data is available. Go takes this a step further by embedding the yields (and more) into both the standard library and the language itself. It's quite a powerful programming model for certain types of work.

When I started running into fiber issues I tried to use threads instead (just to avoid the issues; I have no need of the parallelism or other utilities that they provide in this application) but I ran into a bunch of problems.

First, D's insistence on message-passing, while noble and respectable, is not a perfect fit for cases where I have no need or desire to synchronize large portions of the data structures. In general, this sort of coarse-grained opportunistic parallelism extracts very few additional useful cycles out of multicore hardware compared to a more targeted fine-grained approach. As you note, there's a reason that a lot of the scheduling is starting to move more to user-space "tasks".

(As an aside, it would be awesome to see a Cilk-like work stealing implementation in D. That's by far the easiest first step to really extracting parallelism our of programs and you can often get most of the benefit just with that. It's yet another elegant way to use the call-stack for expressing and exploiting parallelism and dependencies.)

Second, and more importantly, there didn't seem to be a clean way to wait on multiple things in D right now. For instance, I want to yield a thread/fiber until there is either a socket state change, *or* a thread message. This can be a minor problem with fibers, but less-so since the fiber itself can basically just take over whatever work needs to be done rather than trying to wake other threads. There doesn't seem to be a clean way to do this in D currently other than waking threads and basically polling multiple things, which I'm sure you can agree is not ideal.

Stack overflow? Give the fiber a larger stack when you create it. The default is really rather small.

I'm fairly certain that it's not a "real" stack overflow... the program continues to operate normally unless the debugger is stepping through and it only happens when an exception is thrown. And it happens pretty much always when an exception is thrown, you just won't see it unless you have a debugger attached to see the output. So like I said, it is somewhat worrisome, but the program seems to be running properly despite it, so it may be a red herring.

Reply via email to