Threads and processes

When building an application (realtime, embedded, graphical, or otherwise), the developer may want several algorithms within the application to execute concurrently. This concurrency is achieved by using the POSIX thread model, which defines a process as containing one or more threads of execution. A thread can be thought of as the minimum unit of execution, the unit of scheduling and execution in the QNX Neutrino microkernel. A process, on the other hand, can be thought of as a container for threads, defining the address space within which threads execute. A process always contains at least one thread.

Depending on the nature of the application, threads might execute independently with no need to communicate between the algorithms (unlikely), or they may need to be tightly coupled, with high-bandwidth communications and tight synchronization. To assist in this communication and synchronization, the BlackBerry 10 OS provides a rich variety of IPC and synchronization services.

The following pthread_* (POSIX Threads) library calls don't involve any microkernel thread calls:

The following table lists the POSIX thread calls that have a corresponding microkernel thread call, allowing you to choose either interface:

POSIX call Microkernel call Description
pthread_create() ThreadCreate() Create a new thread of execution
pthread_exit() ThreadDestroy() Destroy a thread
pthread_detach() ThreadDetach() Detach a thread so it doesn't need to be joined
pthread_join() ThreadJoin() Join a thread waiting for its exit status
pthread_cancel() ThreadCancel() Cancel a thread at the next cancellation point
N/A ThreadCtl() Change a thread's BlackBerry 10 OS-specific thread characteristics
pthread_mutex_init() SyncTypeCreate() Create a mutex
pthread_mutex_destroy() SyncDestroy() Destroy a mutex
pthread_mutex_lock() SyncMutexLock() Lock a mutex
pthread_mutex_trylock() SyncMutexLock() Conditionally lock a mutex
pthread_mutex_unlock() SyncMutexUnlock() Unlock a mutex
pthread_cond_init() SyncTypeCreate() Create a condition variable
pthread_cond_destroy() SyncDestroy() Destroy a condition variable
pthread_cond_wait() SyncCondvarWait() Wait on a condition variable
pthread_cond_signal() SyncCondvarSignal() Signal a condition variable
pthread_cond_broadcast() SyncCondvarSignal() Broadcast a condition variable
pthread_getschedparam() SchedGet() Get the scheduling parameters and policy of a thread
pthread_setschedparam(), pthread_setschedprio() SchedSet() Set the scheduling parameters and policy of a thread
pthread_sigmask() SignalProcmask() Examine or set a thread's signal mask
pthread_kill() SignalKill() Send a signal to a specific thread

The OS can be configured to provide a mix of threads and processes (as defined by POSIX). Each process is MMU-protected from each other, and each process may contain one or more threads that share the process's address space.

The environment you choose affects not only the concurrency capabilities of the application, but also the IPC and synchronization services the application might make use of.

Even though the common term IPC refers to communicating processes, we use it here to describe the communication between threads, whether they are within the same process or separate processes.

For information about processes and threads from the programming point of view, see Processes and threads and Processes.

Thread attributes

Although threads within a process share everything within the process's address space, each thread still has some private data. In some cases, this private data is protected within the kernel (for example, the tid or thread ID), while other private data resides unprotected in the process's address space (for example, each thread has a stack for its own use). Some of the more noteworthy thread-private resources are:

Each thread is identified by an integer thread ID, starting at 1. The tid is unique within the thread's process.
Each thread has a priority that helps determine when it runs. A thread inherits its initial priority from its parent, but the priority can change, depending on the scheduling policy, explicit changes that the thread makes, or messages sent to the thread.

In the BlackBerry 10 OS, processes don't have priorities; their threads do.

For more information, see Thread scheduling.

You can assign a name to a thread; see pthread_getname_np() and pthread_setname_np(). Utilities such as dumper and pidin support thread names. Thread names are a BlackBerry 10 OS extension.
Register set
Each thread has its own instruction pointer (IP), stack pointer (SP), and other processor-specific register context.
Each thread executes on its own stack, stored within the address space of its process.
Signal mask
Each thread has its own signal mask.
Thread local storage
A thread has a system-defined data area called thread local storage (TLS). The TLS is used to store per-thread information (such as tid, pid, stack base, errno , and thread-specific key/data bindings). The TLS doesn't need to be accessed directly by a user application. A thread can have user-defined data associated with a thread-specific data key.
Cancellation handlers
Callback functions that are executed when the thread terminates.

Thread-specific data, implemented in the pthread library and stored in the TLS, provides a mechanism for associating a process global integer key with a unique per-thread data value. To use thread-specific data, you first create a new key and then bind a unique data value to the key (per thread). The data value may, for example, be an integer or a pointer to a dynamically allocated data structure. Subsequently, the key can return the bound data value per thread.

A typical application of thread-specific data is for a thread-safe function that needs to maintain a context for each calling thread.

Diagram showing sparse matrix (tid,key) to value mapping.

You can use the following functions to create and manipulate this data:

Function Description
pthread_key_create() Create a data key with destructor function
pthread_key_delete() Destroy a data key
pthread_setspecific() Bind a data value to a data key
pthread_getspecific() Return the data value bound to a data key

Thread life cycle

The number of threads within a process can vary widely, with threads being created and destroyed dynamically. Thread creation ( pthread_create()) involves allocating and initializing the necessary resources within the process's address space (for example, thread stack) and starting the execution of the thread at some function in the address space.

Thread termination ( pthread_exit(), pthread_cancel()) involves stopping the thread and reclaiming the thread's resources. As a thread executes, its state can generally be described as either ready or blocked. More specifically, it can be one of the following:

Diagram showing possible thread states.
The thread is blocked on a condition variable (for example, it called pthread_cond_wait()).
The thread has terminated and is waiting for a join by another thread.
The thread is blocked waiting for an interrupt (that is, it called InterruptWait()).
The thread is blocked waiting to join another thread (for example, it called pthread_join()).
The thread is blocked on a mutual exclusion lock (for example, it called pthread_mutex_lock()).
The thread is sleeping for a short time interval (for example, it called nanosleep()).
The thread is waiting for a reply to be delivered across the network (that is, it called MsgReply*()).
The thread is waiting for a pulse or signal to be delivered across the network (that is, it called MsgSendPulse(), MsgDeliverEvent(), or SignalKill()).
The thread is waiting to be executed while the processor executes another thread of equal or higher priority.
The thread is blocked on a message receive (for example, it called MsgReceive()).
The thread is blocked on a message reply (that is, it called MsgSend(), and the server received the message).
The thread is being executed by a processor. The kernel uses an array (with one entry per processor in the system) to keep track of the running threads.
The thread is waiting for a semaphore to be posted (that is, it called SyncSemWait()).
The thread is blocked on a message send (for example, it called MsgSend(), but the server hasn't yet received the message).
The thread is blocked waiting for a signal (that is, it called sigsuspend()).
The thread is blocked waiting for a signal (that is, it called sigwaitinfo()).
The thread is waiting for the virtual address space to be allocated for the thread's stack (parent has called ThreadCreate()).
The thread is blocked waiting for a SIGCONT signal.
The thread is waiting for a noninteger (for example, floating point) context to become available for use.
The thread is waiting for physical memory to be allocated for a virtual address.
The thread is waiting for a child thread to finish creating itself (that is, it called ThreadCreate()).

Last modified: 2015-03-31

Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus