Release 1.0  Copyright ã1994 by Arcane Systems Ltd.  All Rights Reserved.

ThreadKit

Library:	libThreadKit.a

Header File Directory:	/LocalDeveloper/Headers/threadkit

Import:	threadkit/threadkit.h

Introduction

This introduction and the descriptions of classes included with ThreadKit assume some prior exposure to multi-threaded programming.  A brief introduction is provided in the ThreadKit concepts manual:

/LocalLibrary/Documentation/Concepts/ThreadKit.rtf

The ThreadKit defines a set of Objective C classes, a protocol and a category that attempt to solve some of the problems associated with writing multi-threaded applications under NEXTSTEP.

It does not circumvent all of the possible pitfalls that a programmer can fall prey to in a multi-threaded environment.  Any action that wasn't thread-safe before you started using ThreadKit is still not thread-safe... but you can ensure that your application uses it in a safe manner by applying instances of the ThreadKit classes to the problem at hand.

ThreadKit Classes

The ThreadKit is quite small, consisting of four classes: TKApplication, TKObject, TKResource and TKThread, the TKThreadLocking protocol and the Object(TKThreadLocking) category.  The following sections outline the philosophy of the ThreadKit, leaving detailed descriptions of class and category methods to their specific documentation.

ThreadKit Protocol

All classes in the ThreadKit share a common design expressed through the TKThreadLocking protocol.  This protocol specifies six messages:

- threadLock	- threadLockWhen:
- threadUnlock	- threadUnlockWith:
- tryThreadLock	- tryThreadLockFor:

Each ThreadKit class responds to these methods, and all other classes can respond to them when the ThreadKit is being used courtesy of the Object ( TKThreadLocking ) category.

Before discussing what a lock is and how it helps in a multi-threaded environment, a quick description of the different locking messages is in order:

threadLock	Waits until the object in question may be successfully locked, and then locks it.  This may occur occurs in one of two ways:  If the object is not locked by any thread, or if the thread attempting the lock already holds a lock on the object.  The object will keep track of the number of successful locks that have been placed that have not yet been matched by successful unlock operations.

tryThreadLock	Behaves the same as threadLock, except that this message will not wait for a successful lock.  The message simply reports failure if the lock is not immediately available.

threadUnlock	Removes a single lock from the object in question.  Only the thread that currently has the object locked may unlock it.

threadLockWhen:	Similar to threadLock, this method places an additional constraint:  Attempts to place a lock on an object that is currently not locked by the thread will wait until it has been unlocked with a particular state.

tryThreadLockWhen:	Behaves the same as threadLockWhen:, except that this message will not wait for a successful lock.  The message simply reports failure if the lock is not immediately available.

threadUnlockWith:	Removes a single lock from the object in question.  Only the thread that currently has the object locked may unlock it.  If the locked remove is the last remaining lock, the object is placed in a specified state.

The Meaning of a Lock

For most objects the meaning of a lock is quite simple:  No other thread may lock the same object until the lock is removed.  This provides a simple mechanism for implementing atomic actions on data that will run to completion without allowing the data to be corrupted by another such operation.  Note that this is only true if every such action uses the same locking mechanism.

The kit supports this form of thread-safety in Objective-C using two paradigms:  Internally enforced locking and externally enforced locking.

Internally Enforced Locking

Object-oriented programming ensures that all access to an object's data is localized to the code implementing the object's behavior.  This is convenient, because it means that the object itself can manage locks to ensure the integrity of the data within.  If simple cases, making an object thread-safe can be a simple matter of asking the object to lock itself before beginning any operation on its own data and unlock itself when the operation is complete:

[ self threadLock ] ;

...

[ self threadUnlock ] ;

The threadLock and threadUnlock methods should be inherited from TKObject for maximum efficiency.  For cases where this is not possible, such as when subclassing existing classes, the same locking methods are automatically provided through the Object ( TKThreadLocking ) category. Locking operations can be nested to any depth, so calling one such operation from within another is safe.  The lock only prevents a different thread from acquiring a lock.

Once thread-safe locking is implemented in an object, it can be called upon by any number of threads concurrently without the caller needing to be aware of the object's locking requirements.  All ThreadKit classes are written using internally enforced locking.

Externally Enforced Locking

In some cases internal enforcement is impractical or impossible.  One such is the case with objects written by others for which source code is not available, such as the AppKit.  Another is where the the resource being used isn't actually implemented as an object, such as a number of functions that are not thread safe, including printf().  In order to allow these cases to be handled in a thread-safe manner, locking must be performed by those calling on the resource for it to be handled in a thread-safe manner.

The ThreadKit provides two mechanisms to help with this:  The Object ( TKThreadLocking ) category allows locks on any arbitrary object to be managed, so that thread-safety can be ensured.  For example, operations on a HashTable object could be bracketed as follows:

[ aHashTable threadLock ] ;

...

[ aHashTable threadUnlock ] ;

This is done in order to ensure that the operations between locking and unlocking will run to completion before other threads send messages to the HashTable object.  This will only work if every attempt to manipulate the HashTable object is bracketed by lock and unlock operations from the caller's end, which makes it tougher to ensure than internally-enforced locking.

The second mechanism is through the TKResource class, which allows named resources to be tracked and locked.  For example, to lock resources associated with printf(), you could bracket its usage as follows:

[ [ TKResource findResourceFor: "function:printf" ] threadLock ] ;

...

[ [ TKResource findResourceFor: "function:printf" ] threadUnlock ] ;

The actual name chosen is unimportant, so long as every time your application needs a given resource it refers to it by the same name.  This method works equally well for devices, files or anything else you can tag with a name.

Special Meanings for Locks

Locks on both the TKThread and TKApplication classes are interpreted specially.  These special meanings are as follows:

TKThread

While the TKThread class locks and unlocks in the same fashion as other ThreadKit classes, a locked thread will suspend execution the next time the yield class method is invoked.  The thread will remain suspended until all locks have been released, at which time it will continue executing.  This is often a more appropriate behavior than the low level thread_suspend() since it allows the thread itself to control where it may be suspended.

TKApplication

The TKApplication class is quite different from other ThreadKit classes in a number of ways.  It is meant to conceptually represent the NEXTSTEP Application Kit as a whole and provides useful semantics for dealing with the user interface from multiple threads.  Any time a thread needs to interact with an AppKit class, all it needs to do is lock NXApp, which represents the single TKApplication instance in a ThreadKit based application.

[ NXApp threadLock ] ;

...

[ NXApp unlockThread ] ;

The TKApplication instance manages its own state and is always implicitly locked by the application's main thread when not explicitly locked by any other thread.

This has a number of impacts: Firstly, while the same protocol is used to manage locks, tryThreadLock, tryThreadLockFor: always fail because of the implicit lock held by the main thread... he threadLock message always has to wait until it can arrange for the main thread to be put on hold with the AppKit in a state that is safe to use from another thread.  Secondly, threadLockWhen: and threadUnlockWith: always fail because the state of the lock is managed internally.  Finally, there is no need to lock the application kit in code that is only ever executed by the main thread, allowing multi-threaded extensions to be added to existing code with minimal effort.

Deadlock Avoidance

First off, what is a deadlock?  A deadlock occurs when a thread attempts to lock a resource that can never become unlocked.

Under what circumstances can this occur?  The simplest case is when two threads are holding locks that they intend to release, but only after they lock a resource that the other thread already has locked:

- Thread #1 locks resource A
- Thread #2 locks resource B
- Thread #1 attempts to lock resource B
- Thread #2 attempts to lock resource A

Getting a multi-threaded program right takes some effort, but there are a few general rules that can help avoid situations like this:

·	Never hold a lock longer than you need to.  Lock a resource, use it, and then unlock it

·	Don't hold a lock while waiting for user input

·	When you need to lock more than one resource at a time, choose a well defined order to do so in and stick to it to avoid the scenario described above.  For example: Lock functions first, then files, then objects.  Within each category , do the same thing:  Functions alphabetically, object structures from the top down, etc.

Freeing Objects

Special consideration should be given to freeing objects in a multi-threaded environment.  Never free an object that is locked.  This may seem counter-intuitive, but is actually quite sound practice for a variety of reasons.

Firstly, it rarely accomplishes the desired goal.  A lock is usually gained in this case to prevent another thread from using the object that is about to disappear.  In practice, once the object has been freed competing attempts to lock it will result in runtime errors.  A more practical approach is to lock the object that maintains a pointer to the doomed object.

Secondly, the implementation of locking for objects that do not inherit from TKObject cannot detect when a locked object is freed and may mistakenly identify a subsequently created object as having been previously locked.

ThreadKit and Distributed Objects

NEXTSTEP's distributed object mechanism makes use of threads itself for a variety of reasons, including the management of dead connections and running object servers in separate threads.  A distributed object based application will probably want to send the message setRuntimePolicy: TK_ALWAYS to the TKThread class to prevent it from turning the thread-safe Objective-C runtime system off.

Archiving ThreadKit Objects

When unarchiving ThreadKit objects, you'll find that the lock count is always reset to zero regardless of the object's state when archived.  The lock information is entirely oriented towards the management of a system at runtime and is meaningless if preserved.

Note also that attempts to unarchive a TKResource whose name matches an existing instance will not result in the creation of a new TKResource.  A pointer to the existing object will be returned instead.

Lastly, TKThread and TKApplication instances cannot be archived.  The former would be meaningless and the latter takes after the Application class in this respect.

Non-AppKit ThreadKit Programs

If you aren't creating an AppKit-based application, you will need to do some initialization that is normally performed automatically.  Make a single call to TKSetup() before using any ThreadKit facilities, and call TKCleanup() before your application terminates.

You'll also need to include the -ObjC directive when compiling non-AppKit ThreadKit-based programs.  See NeXT's documentation on the Objective-C compiler for details.  Failure to include this flags will result in the message "objc: Object: does not recognize selector +_threadKitClassSetup" at runtime

The Objective-C Runtime Library

The ThreadKit automatically invokes the objc_setMultithreaded() function to make the runtime library thread-safe when new threads are created, and reverts to thread-unsafe operation when all threads but the main one have been destroyed.  Note that this is only true for threads the ThreadKit knows about.  If you can't create all of your threads using the TKThread instances, you'll need to disable this automatic feature using TKThread's setRuntimePolicy: method.

Dynamic linking of new Objective-C classes at runtime is an inherently dangerous thing which is not guaranteed to be thread-safe under any conditions.  In order to provide for this and any other operation that may prove to be equally fragile, the ThreadKit provides a mechanism for suspending all threads but the currently executing one.  See the description of the TKThread class methods startDangerousAction: and endDangerousAction.

Multi-Threaded Gotchas

None of the ThreadKit classes will function properly if your application doesn't allow the ThreadKit to be properly initialized.  The application must either use TKApplication or a subclass thereof as the owner the of the main .nib file, or make calls to TKSetup() before using ThreadKit classes and TKCleanup() before exiting.  If your application seems to crash unexpectedly when using ThreadKit classes, check to make sure that this is the case before pursuing other possibilities.

A lot of library functions aren't thread-safe and I don't know of any compiled list of the dangerous ones.  Notable culprits include but are by no stretch of the imagination limited to: usleep(), ctime() and the whole printf() family.