Open Class Threading Model

A typical application is a single-threaded process. The path-of-execution enters the main() function, runs the application and library methods, and terminates as it exits main(). Within a process, the thread runs your code.

However, many threads may execute simultaneously within a single process, independent to each other and to the main thread. Processes running more-than-one thread are "multi-threaded." Multiple processes running concurrently may also be multi-threaded.

The operating systems that support IBM Open Class (Windows NT, OS/2, and AIX) are themselves multi-threaded; therefore multi-threaded applications can take advantage of hardware platforms that have multiple CPU devices. Threaded Open Class applications can achieve tremendous performance gains over the default, single-threaded, applications.

You must apply multiple threads programmatically. Each thread can run your application's global methods and objects' methods. Sometimes two or more threads are calling methods on the same object simultaneously. Several threads may simultaneously call the same method or multiple methods that access the same data belonging to the application object. When multiple threads contend for access to the same data, you must synchronize, or serialize, their access.

To protect application data from simultaneous access by multiple threads of execution, the Open Class library provides mutex locks, referred to as resources, and event locks, referred to as conditions. Open Class classes and/or methods that are themselves protected from simultaneous access to multiple threads of execution are explicitly documented as "Thread-Safe." Assume any Open Class class or method that is not documented as Thread-Safe to not be Thread-Safe.

Open Class application objects typically use thread objects; unlike objects in other threading models, they do not derive from a thread object or implement a threading interface.

GUI and Non-GUI Threads

Open Class has two concrete base thread classes:

INonGUIThread is the basic thread class; however, IThread, which is derived from INonGUIThread, offers additional threaded behavior for graphical user interface situations. To start a thread within a process, you must explicitly use an object of either INonGUIThread or IThread.

These two thread classes have no application state of their own. They share access to the application objects' data.

Data Sharing

The behavior of a multi-threaded process behavior is non-deterministic; you don't know:

When C++ data is only accessible through methods, synchronizing the access to those methods synchronizes access to the data. To ensure that no more than one-thread-at-a-time is modifying the same data or that data is not being read by one thread while another is modifying it, you can either:

It is recommended that you not synchronize IThread objects, because they are GUI threads. If IThread objects block, or sleep, they might delay the response of the graphical user interface, depending upon the operating system. For extended operations by an IThread object, that object should launch an INonGUIThread object which can be synchronized instead.

The Thread Currently Running an Application Object's Method

The application object's method does not know which thread is calling on it, but can use the ICurrentThread class to obtain a reference to the IThread or INonGUIThread object: use IThread::current. Open Class allows only a single object of ICurrentThread. You can use ICurrentThread to

To restrict the threaded execution of an application method to the thread currently running, override ICurrentThread::run to call the application method.

Locking a Resource

An IResource object is a lock you can use to serialize thread access to code. Typically, a class declares a single static IResource object that is shared by all the objects of this class. IResource is an abstract base class. IResource objects are instances of either subclass:

An application object must have a lock in every public and protected method that accesses the same data. You would not need to put a lock in private methods, as long as all the public/protected methods which call that private method have locks in their body that precede the call. The following method is structured for the first thread entering the method's body to lock the static resource, thereby blocking any other threads from calling this code until the owning thread exits this method.

class myClass {

IPrivateResource myLock;

myClass::myLockedMethod() {

IResLock mySetLock; // locking for the remainder of myLockedMethod occurs here

... // single-threaded access assured for the remainder of myLockedMethod

} // mySetLock is deleted and myLock is unlocked when myLockedMethod goes out of scope

}

The IResLock object implicitly calls IPrivateResource.lock and IPrivateResource.unlock. Open Class automatically calls the IResLock destructor if either the thread exits the bracketing method or if an exception is thrown. IResLock saves you from explicitly calling its destructor or from using try/catch blocks for exceptions which depend upon destroying the IResLock object.

Event-driven Synchronization

Class ICondition monitors for an event. An ICondition object blocks all threads from access to the subsequent code. The ICondition object can signal the first waiting thread to run the protected code, or it can broadcast to unblock all of the waiting threads .

Unshared Thread Data

Per-thread data never needs to be synchronized. You can create thread-specific data (per-thread instance data) using the following steps:

  1. Derive your application object from IThreadFn.
  2. Create the data variables you want within your derived class.
  3. Implement the virtual IThreadFn::run method to use that data.

Application objects derived from IThreadFn must also use an INonGUIThread or IThread object to start a thread's execution.

Open Class provides another technique for creating per-thread data. You can use the template class IThreadLocalStorage to create per-thread global variables that may be needed by a library or application.These variable serve as thread-specific global pointers. An IThreadLocalStorage object cannot be instanced by new(), nor can be declared by any object or within the main() function. An IThreadLocalStorage object must be declared globally.

An IThreadLocalStorage object can access a single type of data, which will have an initial value of 0. If you want many types of data referenced by an IThreadLocalStorage object, create a helper class that contains all the desired data types, and then pass it to the IThreadLocalStorage template construction. For optimal performance, do not create a separate IThreadLocalStorage object for every type of data you want this thread to store.

In previous versions of Open Class, IThread handled thread-local storage (which is still supported). However, IThreadLocalStorage improves the implementation.

Scheduling

Often an application creates more threads than the hardware had processors. You can designate some threads to be run before considering running other threads by assigning relative priorities to the threads; this is how you can schedule the multi-threaded execution. In previous versions of Open Class, thread scheduling was based on the OS/2 model (which is still supported). However, new scheduling application code is portable to Windows NT and AIX as well as OS/2.

Open Class prioritizes execution using a priority class and a priority level. In the OS/2 model, both processes and threads had their own priority classes and their own priority levels. Priority classes were specified with an enum, and priority levels were specified with integers (available in the OS/2 system's scheduling).

Scheduling in this release uses the Windows NT model: processes can only have a priority class, and threads can only have a priority level. Thus, the process automatically sets the priority class for all of its threads, but each thread can set/rest its own priority within the process. Both process priorities and thread priorities are specified as enums.

Process scheduling uses the following enum:

enum INonGUIApplication::EProcessPriority

Thread scheduling uses the following enum:

enum INonGUIThread::EThreadPriority

New or Enhanced Process/Thread Classes

The following table lists the Open Class classes that are new or extended in this release:

Class Functionality
IExternalProcess Basic facilities for starting and controlling the execution of processes
IThreadLocalStorage Provides a portable way to create and access per-thread global data
IEnvironment Specifies the execution environment for use with a process
ICondition A class to be used for thread synchronization. When used in conjunction with IResource, provides the classic "Monitors and Conditions" construct
IPrivateCondition Concrete subclass of ICondition, for use within a process
ISharedCondition Concrete subclass of ICondition, for use between processes on a single computer