views:

66

answers:

4

I am supposed to implement a userlevel threads library in C. To do so, I need to implement yield(), createThread() and destroyThread() functions. I believe I've got the basics right:

To keep track of threads, I'll use a queue of ThreadControlBlock elements (which resemble PCBs in an OS) that look like this:

struct ThreadControlBlock { 
    int ThreadId, 
    u_context *context };

We can use the setcontext() family of functions to "save" and "load" the context. Upon initialization of the program, initialize ThreadQueue with no elements.

Now the part I am not getting: when a thread calls yield(), I get the current context and save it in a ThreadControlBlock and put in the queue. Then get the first element in the queue and load the context in it, and execution proceeds.

The question is, if I do this, say I am a thread calling yield() and the next thread is myself. If I am saving the context and loading it again, upon re-entering wouldn't I be at the exact same spot where I was (before calling yield()?) And this would keep going on forever?

A: 

It's perfectly reasonable in your yield() implementation to check to see if the next thread is the current thread and treat that case as a no-op.

Aaron Klotz
We've been told not to do that...
pessimopoppotamus
@pessimopoppotamus: if there are no other threads, then you have to do it. Funny example from the task schedulers: none of them supports 0 tasks. That's why Windows has "System Idle Task" and *NIX has the "init". So that scheduler always has something to schedule. `yield()` as a call to scheduler, is essentially a way to allow [cooperative multi-tasking](http://en.wikipedia.org/wiki/Computer_multitasking#Cooperative_multitasking.2Ftime-sharing). If there is nobody to "cooperate" with, `yield()` can't do anything.
Dummy00001
+1  A: 

When a thread calls yield(), you have to save the state of a thread that's about to return from a yield() call. Don't save the context from immediately before the yield().

Nathon
A: 

If there are no other threads to run besides the current thread then there is nothing else to do besides just return from yield. I wouldn't bother with calling swapcontext in this case, though -- just detect and return.

I think that what you are actually dealing with is what to do when no threads (including the current one) when yield is called. An easy way to deal with this is to have an idle thread, which is only run when the run queue (ready threads) is empty. This thread will probably just:

   {
       while (1) {
           yield();
           pause();
       }
   }

This allows your program to go to sleep (via pause) until a signal happens. Hopefully the signal will be some event that makes one of the other threads ready to run, so the next call to yield will run the other thread instead of running the idle thread again.

nategoose
A: 

The same issue actually applies if you're switching to another task, too - since that other task saved its context at the same point (where it was about to switch to a second task). Using setcontext() and getcontext(), you need to use a static variable to keep track of whether you're switching in or switching out:

static volatile int switched;

switched = 0;
getcontext(current->context);
if (!switched)
{
   switched = 1;
   setcontext(next->context);
}

Alternatively, you can just use swapcontext(current->context, next->context);

caf