We discussed the solution to the third homework. Points to take away from the discussion:
You should always release the mutual exclusion lock
We spent half of the discussion time going over the thread library
struct/class thread_t {
    ....
    ucontext_t thread_context;
}

thread_libinit() {
    initialize internal data structures (locks, condition variables
    /* remember that this function has to be called exactly onces */
    create_thread() {
       thread_t *t = malloc() /* create a new data structure */

       /* code below is from your project handout */ 
       /* first, you need initialize the ucontext_t data structure before it can be used.
        * it is done by calling "getcontext". You MUST call this function for every new
        * new thread that you create!
        */
       getcontext(&t->thread_context); 
       /* Now you need to allocate memory for the stack for this new thread.
        * ucontext_t data structure has a field that points to the memory of the stack.
        * also, you need to tell how much memory you've allocated for the stack.
        */
       t->thread_context.uc_stack.ss_sp = malloc(262144) /* assign allocated memory */
       t->thread_context.uc_stack.ss_size = 262144;  /* this is size */
       .... /* look what else you need to do here in the project handout */
       /* Now you need to initialize another field inside the ucontext_t data structure.
        * and you do so by calling the function "makecontext". You specify where you
        * would like this thread to start executing
        */
       makecontext(&t->thread_context, start_function, 2, arg1, arg2);
       /* the new thread will start in the function "start_function(arg1, arg2)" */
       /* we talked about where you need to start executing your new thread */
   
       /* now you've created and initialize your new thread. To execute your new thread, you can
        * either use "setcontext" or "swapcontext"
        */
       ....
       add new thread to the ready queue
       }
    you've created you new thread. for instance, to start a thread you can do
     setcontext(&t->thread_context);
}

schedule_next() {
    if ready queue is empty, exit;
    else
       old =put current thread on the ready queue
       new = take a thread from the ready queue
       /* while setcontext() just starts execution of the new ucontext_t data structure,
        * swapcontext() saves the information about the currently running (ucontext_t) thread
        * then is start execution specified by the 2nd argument to the function.
        */
       swapcontext(old, new);
}

Remember that some function don't give control of the currently executing thread while others do.