CXFiber

From cxwiki

A "fiber" is a cooperatively-scheduled thread. The CXFiber class allows a single native thread (eg. the Main Thread, or a CXThread thread) to be split into several cooperative threads.
 
The advantage of cooperatively-scheduled threads over preemptively-scheduled threads is that they are often much easier to work with, since data accesses do not need to be protected by mutexes, etc. The advantage of preemptively-scheduled threads is that one thread can respond with low latency while another thread is performing a long-running task. CXFiber offers some of the benefits of each. It is not intended as a general-purpose programming model (use CXThreads or CXWorkerHost tasks where possible) but it vastly simplifies certain specific types of operation.
 
Each CXFiber is parented to a particular CXThread. Effectively, constructing a fiber involved splitting the single thread into multiple fibers. The bundle of (parent thread + fibers) is here termed a "thread group". Just as a process has a main thread and subsiduary threads, so a thread group has the thread itself and its subsiduary fibers. Just as the subsiduary threads must terminate before the main thread, so the subsiduary fibers must terminate before the group's parent thread.
 
// Construct a CXFiber, executing the specified function. The fiber is an
// offshoot of the current thread group.
explicit CXFiber(cx_function<void (void)> function);

// Implicitly calls Stop(), so it's not necessary to call that immediately
// before destroying the fiber.
~CXFiber(void);

// Called from the parent thread, this wakes the fiber and suspends the 
// parent thread until the fiber sleeps.
void Update(void);

// Called from any fiber in the group, this returns true if this fiber is
// currently running. The return value is unaffected by whether this
// fiber is currently awake or sleeping.
bool IsRunning(void);

// Called from the parent thread, this notifies this fiber to stop 
// execution, and then blocks the parent thread under this fiber
// has completed execution. Care must be taken in the fiber to
// ensure that the stop signals are honored, and that the fiber
// doesn't deadlock waiting on the main thread to do something
// else while the main thread is waiting on the fiber to stop.
void Stop(void);

// Called from either the parent thread or this fiber itself,
// this notifies the fiber to stop execution.
void Stopping(void);

// Called from either the parent thread or this fiber itself,
// this checks whether this fiber has been signalled to stop.
bool WantToExit(void) const;

 

// Called from a fiber, this will pass control to the next 
// fiber or to the owning thread in round-robin fashion.   
// If this fiber has been signalled to stop, Sleep() will   
// instead return false as an indication that sleeping is
// not possible.
static bool Sleep(void);

// Returns the CXFiber which owns the current thread of
// execution. Returns nullptr if the current thread of
// execution is not a CXFiber.
static CXFiber* __nullable GetCurrentFiber(void);

// Returns the CXThreadID of this thread group's parent
// thread. If called on the owning thread, or on any 
// non-fiber thread, this is equivalent to calling
// CXThread::GetCurrentThreadID().
static CXThreadID GetEffectiveThreadID(void);

 

 

Use Case

Consider a system which should remain responsive at all times and which was written as single-threaded. If we wish to introduce a new long-running funtion, we need to find some way to ensure that it does not block the system. There are various options:
  • We could break the new function into numerous components that each run for a brief interval and then return to the caller. This is not possible if we need to call some kind of external blocking functions (such as blocking IO) and can be very difficult even if we're just performing computation since we have to write in a specific style designed to be interruptable without losing state.
  • We could implement the new function on a preemptive thread, however it would not have direct integration to our (single-threaded) system. If tight integration was required, this could quickly become very problematic.
  • We could re-write our system to be multi-thread aware. This is not necessarily a bad option, however it is typically very time consuming to convert single-threaded code into multi-thread safe code; often a ground-up rethink is necessary to avoid introducing subtle bugs.
  • We could spawn a CXFiber and perform the long-running task there. The CXFiber would sleep regularly, to ensure that the original system remained responsive, including sleeping during any external blocking functions. Few changes to the existing code would be necessary, since it remains single-threaded.

It is clear the the CXFiber option can often provide many of the upsides of the alternative approaches with less expenditure of development and testing resources, and without the developer even necessarily having to have a strong understanding of the pros and cons of preemtive threading.

 

Restrictions

The CXFiber implementation may operate by creating one native thread per fiber, and use internal mutexes to ensure that only one of them is running at a given time. For this reason, any function or mechanism which is restricted to use on a single thread may not have undefined behaviour when called from a CXFiber. Specifically:
  • Native user interface components which must be accessed on the Main Thread cannot be accessed from a CXFiber derived from the main thread.
  • c++ thread_local variables may be CXFiber-specific (currently always the case) or may be CXThread-specific (potentially in some alternative future implemention).
  • CXThread::GetCurrentThreadID() and etc. may return different results for each CXFiber. CXFiber::GetEffectiveThreadID() should be used instead if a shared CXThreadID is required.