Task

Task Control Block.

The concept of a process embodies 2 characteristics:

  • the unit of dispatching is usually referred to as a thread or lightweight process
  • the unit of resource ownership is usually referred to as a process or task

uC++ _Task (CS343)

uC++‘s _Task is the full concurrency abstraction: thread + stack + mutual exclusion. It’s a coroutine (distinguished main() member, own execution state) with its own thread of control that begins executing main on creation, plus monitor-style mutex on its public members.

_Task Worker {
    int id;
    void main() {                // thread starts here on creation
        for ( int i = 0; i < 100; i += 1 ) {
            // do work...
            if ( someCondition ) _Accept( pause );  // external scheduling
        }
    }
public:
    Worker( int id ) : id( id ) {}
    void pause() {}              // mutex member — client calls this
    void stop() {}
};

Derivation from object properties

thread?stack?+ mutex?= abstraction
NoNo— / _Mutexclass / monitor
NoYes— / _Mutex_Coroutine / _Cormonitor
YesNo— / _Mutexrejected (thread with no stack nonsensical)
YesYes— / _Mutex_Task

Missing thread ⇒ borrows caller’s thread. Missing stack ⇒ no suspend/resume state.

Scheduling

How _Task is implemented (Buhr §9.2)

Every _Task has the same runtime-managed queue set as a _Monitor, plus a mutex queue per member:

                                        entry queue (C)
                                             b
              mutex queues
                  X          Y              d    order of
                  b          d              c    arrival
                  a          c              a

  condition
      A
                                                         acceptor /
                               shared vars              signalled stack (S/W)
  condition
      B
                                exit
          active task      blocked task       duplicate entry
  • Entry queue (C), newly-arrived callers to any mutex member; entered before they know which member they’ll be routed to
  • Mutex queues (one per member), callers routed to a specific member (e.g. insert vs remove); _Accept( insert ) picks from the insert queue
  • Condition queues (A, B, …), tasks that called uCondition::wait, internal scheduling
  • Acceptor/Signalled stack (S), LIFO stack of task’s own thread when it _Accepts or signals; when the accepted call finishes, the top of S resumes

Priority C < W < S means the acceptor/signaller runs next, then signalled tasks, then fresh entry-queue arrivals, this is what “no barging” concretely enforces. See Monitor Types.

_Accept just pushes onto S

_Accept( insert ) sets a filter “only dequeue from the insert mutex queue” and pushes the current task onto S. The scheduler then picks an insert caller off its mutex queue, runs it to completion (or until it waits), then pops S and resumes the acceptor. Nested _Accepts form a stack of blocked acceptors, each resumes only after the one above it exits.

Destructor accept

~Task is a mutex member. _Accept( ~Task ) in main() causes the thread to block until some client calls delete (or the declaring scope exits), which dequeues the destructor from its mutex queue like any other call. See Accepting the Destructor.