ZThreads - Design Overview

This document will be replaced with something more complete in the future.


Introduction
Motivation for ZThreads Creation
Motivation for Interruptable Threads

Introduction

The purpose of this document is to explain some of the motivation for creating ZThreads. This document is long overdue, but hopefully it will help to give people a better understandsing of the core of the library and how to use it.

Other articles will disccuss porting the library, as well as using the higer level constructs it provides.

Motivation for Creation of ZThreads

I have a strong interest in object-oriented design and in design patterns. I also do alot of programming on different systems, some of that programming inevitably involves writting multi-threaded code. C++ doesn't have any built in support for threads. Various platforms provide various methods for creating and managing threads, none of which are very portable. For example, there are POSIX threads, Win32 threads and so on. All of these models of threading are really quite different, which makes writing portable that uses threads somewhat of a challange. My goal was to abstract away the need to rely on any one specific thread model. The primary goals are to create portable and to complete abstract the system specific details involved in writing multi-threaded code.

Once this kind of support is available, much more time can be spent on evolving design patterns for using multi threaded. Developing new patterns, such as the Executor and Future patterns shoudl be beneficial to everyone.

Should C++ have built in support for threads? I'm not sure that is completely necessary. What would go a long way towards improving support for code that threads would be providing a way to deal with related issues, such as a few standard atomic operations (TAS, CAS, DCAS, ...) and perhaps chaning volatile or mutable to ask the compiler to insert a memory barrier. Something like that, coupled with a way to handle the order certain objects are destroyed in (a more flexible atexit() function) would add alot of power to the language and to what you can do with threads without having to pick any one model for multithreading. This library demonstrates that flexible, portable support for threads, including a cancellation-like mechanism can be built on top of current thread models.

Motivation for Interrupting Threads

There are several reasons I choose to create an interruptible Thread model. Often, there is a need to abort a blocking operation. For example, a thread might be put to sleep on a condition variable only to have some event occur in a program that would make it desirable to wake that thread up again. Each platform provides a method for accomplishing this, but each method is very closely tied to that platform. The two major models of this are POSIX cancellation and Win32 WaitForMultipleObjects(). The following explains the challanges in emulating the behavior of either model and the portable alternative ZThreads provides.

Problems with POSIX Cancellation:

Cancellation is a C based mechanism; because of this it does not safely interact with exceptions. A thread cancelled within a try block will not neccessarily unwind the stack correctly; this makes it extremely difficult to write safe code that uses cancellation with C++ exceptions.

This library is intended to be object-oriented. Pushing function pointers onto a clean up stack can be somewhat awkward to incorporate nicely into an object-oriented design.

Cancellation is not completely portable. Although it is standard, different operating systems have different levels of support for this mechanism. Some cancellation points are optional, and some cancelltions are non-standard.

When writing code for a single system it is not at all difficult to lookup the supported cancellation points. However, writing portable code would require finding out about each platforms cancellation support. This is tedious at best. If this is not done correctly it is very possible to write code on one system that does not behave the same way on another system, or to write code that relies on a cancellation that exists on one system, but not on another.

It is possible to turn cancellation on and off for various regions of code, but this really leads to the same problem. A programmer can easily forget to do this and get the expected behavior on one system only to find that porting another system has brought about a series of unexpected cancellation problems.

This library is all about abstraction and portability. An important goal is to remove the burden of dealing with the specific details of each system from the programmer.

Cancellation is an invasive mechansim. Invoking the cancellation on a thread has the potentialy to disturb non-pthreads functions. This is by design and again is not an issue when writing code for one system. However, it becomes a tricky issue when writing portable code.

Emulating this kind of behavior would require creating wrappers for functions that should act as cancellation points for systems that don't support POSIX cancellation (Win32). This in completely outside the scope of a thread library. This libraries focus is on portable threads, not on creating a portable version of libc that supports cancellation.

Additionally, the I/O models uses by different systems are very different. For example, Win32 uses compeletion ports, event handles, winsock, etc. It is not possible to choose a single set of functions that are appropriate cancellation points for all systems because of these differences.

Problems with Win32 WaitForMultipleObjects():

WaitForMultipleObjects(), like POSIX cancellation, is also not C++ exception based. Although it does not have the same problems in interacting with exception based code, it can still be a bit more awkward to deal with error codes and switch statements.
WaitForMultipleObjects() is based on Windows handles. It is very versatile because it allows you to wait on a set of handles and to the end that wait when any of those handles is signaled. Just about everything in Windows uses handles making it possible to do very interesting things like wait on thread handle, a socket handle, a file handle and an event handle.

The main drawback is that on other systems not everything uses the same opaque type to create generic handles. For example, on POSIX file handles (int), thread handles (pthread_t) and mutex handles (pthread_mutex_t) are unrelated. It very difficult to create a POSIX implementation of that function with the same versatility. Without the ability to wait on several kinds of handles a big part of the usefulness of WaitForMultipleObjects() would be lost.

Another difficulty in using this type of approach is that, just like POSIX cancelation, it can be easy to misuse. Programmers must be very aware of the consequences involved in using event handles correctly. While this can be chalked up to programmer responsibility, this library is an abstraction. Its intended to take the focus away from these lower level details.

Interruption as a Solution:

To deal with these problems some type of solution was needed. First and foremost, this solution should be 100% portable. The primary goal is portability and abstraction. Remember that this library is not intended to replace pthreads or win32 threads or any operating systems native thread support. Its meant to take advantage of that support and to extend it to provide a truely portable abstraction for using threads.

Interruption is a completely C++ exception based mechanism. Stack unwinding is done correctly, and there are no issues involved with using interruption safely. Its very natural to use interruption if you are used to writing exception based code. No function pointers are required.
Interruption is a completely portable mechanism. There are no issues involved with unexpected behavior when porting interruption-based code to another system. A program using interruption will remain consistent n its behavior.
Interruption is far more flexible. Cancellation is a terminal operation, while interruption is not. Cancellation has cancel-and-exit semantics, which is to say that once a thread is cancelled it has to die. This can be limiting in what can be accomplished.

Cancellation was not intended to be a communication mechanism.

Interruption, on the other hand, is temporary. A thread will recieve an exception when interrupted, but it will reset to a normal state once that exception is thrown; it is not forced to terminate. The thread can go on to do other things, which adds a huge amount of flexibility to what can be accomplished. If a programmer prefers to use cancel-and-exit semantics, they can take an exception thrown as a result of interruption to mean they need to shut down and exit. However, if they want to expliot the flexiblity of interruption for other purposes, that can also be accomplished.

Interruption is an important communication mechanism.

Interruption is not an invasive mechanism. Interruption is focused on threads and thread related operations. by default it will not affect functions outside of the ZThreads library. This is important for portability.

However, interruption does provide a hook that lets a programmer tie some system specific behavior to he interruption mechanism. This can be used to make interruption have some effect on functions external to the ZThreads library if this is desired.

This is the only, and first, free library that provides this kind of portable C++ support.

 Copyright © 2000 - 2002,  Eric Crahen <crahen at cs dot buffalo dot edu> - All rights reserved.
Permission to use, copy, modify, distribute and sell this documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. Eric Crahen makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.