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".)
Warning
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.
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
)
else
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.
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.
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
)
{
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.
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.
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.
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.
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:
Warning
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.
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.