The Kernel Kit: Thread and Team Concepts

The Kernel Kit Table of Contents | The Kernel Kit Index

# Thread and Team Concepts

A thread is a synchronous process that executes a series of program instructions. A team is a group of threads that make up a single program or application.

Every application has at least one thread: When you launch an application, an initial thread—the main thread—is automatically created (or spawned) and told to run. The main thread executes the ubiquitous `main()` function, winds through the functions that are called from `main()`, and is automatically deleted (or killed) when `main()` exits.

The Be operating system is multithreaded: from the main thread you can spawn and run additional threads; from each of these threads you can spawn and run more threads, and so on. All the threads in all applications run concurrently and asynchronously with each other.

Threads are independent of each other. Most notably, a given thread doesn't own the other threads it has spawned. For example, if thread A spawns thread B, and thread A dies (for whatever reason), thread B will continue to run. (But before you get carried away with the idea of leap-frogging threads, you should take note of the caveat in "Death and the Main Thread".)

Threads and the POSIX fork() function are not compatible. You can't mix calls to `spawn_thread()` (the function that creates a new thread) and `fork()` in the same application: If you call `spawn_thread()` and then try to call `fork()`, the `fork()` call will fail. And vice versa.

Although threads are independent, they do fall into groups called teams. A team consists of a main thread and all other threads that "descend" from it (that are spawned by the main thread directly, or by any thread that was spawned by the main thread, and so on). Viewed from a higher level, a team is the group of threads that are created by a single application. You can't "transfer" threads from one team to another. The team is set when the thread is spawned; it remains the same throughout the thread's life.

All the threads in a particular team share the same address space: Global variables that are declared by one thread will be visible to all other threads in that team.

### Spawning a Thread

You spawn a thread by calling the `spawn_thread()` function. The function assigns and returns a system-wide `thread_id` number that you use to identify the new thread in subsequent function calls. Valid `thread_id` numbers are positive integers; you can check the success of a spawn thus:

       thread_id my_thread = spawn_thread(...);

       if ((my_thread) < B_OK)
          /* failure */
       else
          /* success */

The arguments to `spawn_thread()`, which are examined throughout this description, supply information such as what the thread is supposed to do, the urgency of its operation, and so on.

#### Threads and App Images

A conceptual neighbor of spawning a thread is the act of loading an executable (or loading an app image). This is performed by calling the `load_image()` function. Loading an image causes a separate program, identified as a file, to be launched by the system. For more information on the`load_image()` function, see Images.

### Telling a Thread to Run

Spawning a thread isn't enough to make it run. To tell a thread to start running, you must pass its `thread_id` number to either the `resume_thread()` or `wait_for_thread()` function:

- `resume_thread()` starts the new thread running and immediately returns. The new thread runs concurrently and asynchronously with the thread in which `resume_thread()` was called.

- `wait_for_thread()` starts the thread running but doesn't return until the thread has finished. (You can also call `wait_for_thread()` on a thread that's already running.)

Of these two functions, `resume_thread()` is the more common means for starting a thread that was created through`spawn_thread()`. `wait_for_thread()` is typically used to start the thread that was created through `load_image()`.

### The Thread Function

When you call `spawn_thread()`, you must identify the new thread's thread function. This is a global C function (or a static C++ member function) that the new thread will execute when it's told to run. The thread function, defined as `thread_func`, takes a single (`void *`) argument and returns an `int32` error code. When the thread function exits, the thread is automatically killed.

You pass a thread function as the first argument to `spawn_thread()`. For example, here we spawn a thread that uses a function called `lister()` as its thread function. The last argument to spawn_thread() is forwarded to the thread function:

       int32 lister(void *data)
       {
          /* Cast the argument. */
          BList *listObj = (BList *)data;
          ...
       }

       int32 main()
       {
          BList *listObj = new BList();
          thread_id my_thread;

          my_thread = spawn_thread(lister, ..., (void *)listObj);
          resume_thread(my_thread);
          ...
       }

See Passing Data to a Thread for other methods of passing data to a thread.

### Thread Names

A thread can be given a name which you assign through the second argument to `spawn_thread()`. The name can be 32 characters long (as represented by the `B_OS_NAME_LENGTH` constant) and needn't be unique—more than one thread can have the same name.

You can look for a thread based on its name by passing the name to the `find_thread()` function; the function returns the `thread_id` of the so-named thread. If two or more threads bear the same name, the `find_thread()` function returns the first of these threads that it finds.

You can retrieve the `thread_id` of the calling thread by passing `NULL` to`find_thread()`:

       thread_id this_thread = find_thread(NULL);

To retrieve a thread's name, you must look in the thread's `thread_info` structure. This structure is described in the `get_thread_info()` function description.

Dissatisfied with a thread's name? Use the `rename_thread()` function to change it. Fool your friends.

### Thread Priorities

In a multi-threaded environment, the CPUs must divide their attention between the candidate threads, executing a few instructions from this thread, then a few from that thread, and so on. But the division of attention isn't always equal: You can assign a higher or lower priority to a thread and so declare it to be more or less important than other threads.

You assign a thread's priority (an integer) as the third argument to `spawn_thread()`. There are two categories of priorities: "time-sharing" and "real-time."

- Time-sharing (values from 1 to 99). A time-sharing thread is executed only if there are no real-time threads in the ready queue. In the absence of real-time threads, a time-sharing thread is elected to run once every "scheduler quantum" (currently, every three milliseconds). The higher the time-sharing thread's priority value, the greater the chance that it will be the next thread to run.

- Real-time (100 and greater). A real-time thread is executed as soon as it's ready. If more than one real-time thread is ready at the same time, the thread with the highest priority is executed first. The thread is allowed to run without being preempted (except by a real-time thread with a higher priority) until it blocks, snoozes, is suspended, or otherwise gives up its plea for attention.

The Kernel Kit defines seven priority constants (see Thread Priority Values for the list). Although you can use other, "in-between" value as the priority argument to `spawn_thread()`, it's suggested that you stick with these.

Furthermore, you can call the `suggest_thread_priority()` function to let the Kernel Kit determine a good priority for your thread. This function takes information about the thread's scheduling and CPU needs, and returns a reasonable priority value to use when spawning the thread.

### Synchronizing Threads

There are times when you may want a particular thread to pause at a designated point until some other (known) thread finishes some task. Here are three ways to effect this sort of synchronization:

- The most general means for synchronizing threads is to use a semaphore. The semaphore mechanism is described in great detail in

  ### Semaphores

  .

- Synchronization is sometimes a side-effect of sending data between threads. This is explained in "Passing Data to a Thread", and in

  ### Ports

  .

- Finally, you can tell a thread to wait for some other thread to die by calling `wait_for_thread()`, as described earlier.

### Controlling a Thread

There are four ways to control a thread while it's running:

- You can put the calling thread to sleep for some number of microseconds through the `snooze()` and `snooze_until()` functions.

- You can suspend the execution of any thread through the `suspend_thread()` function. The thread remains suspended until you "unsuspend" it through a call to `resume_thread()` or `wait_for_thread()`.

- You can send a POSIX "signal" to a thread through the `send_signal()` function. The `SIGCONT` signal tries to unblock a blocked or sleeping thread without killing it; all other signals kill the thread. To override this behavior, you can install your own signal handlers.

- You can kill the calling thread through `exit_thread()`, or kill some other thread through `kill_thread()`. Feeling itchy? Try killing an entire team of threads: The`kill_team()` function is more than a system call. It's therapy.

#### Death and the Main Thread

As mentioned earlier, the control that's imposed upon a particular thread isn't visited upon the "children" that have been spawned from that thread. However, the death of an application's main thread can affect the other threads:

When a main thread dies, the game is pretty much over. The main thread takes the team's heap, its statically allocated objects, and other team-wide resources—such as access to standard IO—with it. This may seriously cripple any threads that linger beyond the death of the main thread.

It's possible to create an application in which the main thread sets up one or more other threads, gets them running, and then dies. But such applications should be rare. In general, you should try to keep your main thread around until all other threads in the team are dead.

### Passing Data to a Thread

Every thread has a message cache. You can write to a thread's message cache through the `send_data()` function. The thread can pick up your message (a combination of an integer and a buffer) through `receive_data()`. The cache is only one message deep; if there's a message already in the cache, send_data will block. Conversely, if there's no message in the cache, receive_data() will block.

You can also pass data to thread through a port. Arbitrarily deep, ports are more flexible than the message cache. See Ports for details.

The Kernel Kit Table of Contents | The Kernel Kit Index

The Be Book,
...in lovely HTML...
for BeOS Release 5.

Copyright © 2000 Be, Inc. All rights reserved..