Now that we've seen how to start another process, let's see how to start another thread. Any thread can create another thread in the same process; there are no restrictions (short of memory space, of course!). The most common way of doing this is via the POSIX pthread_create() call:
#include <pthread.h>
int
pthread_create (pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
The pthread_create() function takes four arguments:
Note that the thread pointer and the attributes structure (attr) are optional—you can pass them as NULL.
The thread parameter can be used to store the thread ID of the newly created thread. Notice that in the following examples, we pass a NULL, meaning that we don't care what the ID is of the newly created thread. If we did care, we could do something like this:
pthread_t tid;
pthread_create (&tid, …
printf ("Newly created thread id is %d\n", tid);
This use is actually quite typical, because often you want to know which thread ID is running which piece of code.
The new thread begins executing at start_routine(), with the argument arg.
When you start a new thread, it can assume some well-defined defaults, or you can explicitly specify its characteristics. Before we jump into a discussion of the thread attribute functions, let's look at the pthread_attr_t data type:
typedef struct {
int __flags;
size_t __stacksize;
void *__stackaddr;
void (*__exitfunc)(void *status);
int __policy;
struct sched_param __param;
unsigned __guardsize;
} pthread_attr_t;
Basically, the fields are used as follows:
The following functions are available:
This looks like a pretty big list (20 functions), but in reality we have to worry about only half of them, because they're paired: get and set (with the exception of pthread_attr_init() and pthread_attr_destroy()).
Before we examine the attribute functions, there's one thing to note. You must call pthread_attr_init() to initialize the attribute structure before using it, set it with the appropriate pthread_attr_set*() function(s), and then call pthread_create() to create the thread. Changing the attribute structure after the thread's been created has no effect.
The function pthread_attr_init() must be called to initialize the attribute structure before using it:
... pthread_attr_t attr; ... pthread_attr_init (&attr);
You can call pthread_attr_destroy() to uninitialize the thread attribute structure, but almost no one ever does (unless you have POSIX-compliant code).
In the descriptions that follow, the default values are marked (default).
The three functions, pthread_attr_setdetachstate(), pthread_attr_setinheritsched(), and pthread_attr_setscope() determine whether the thread is created joinable or detached, whether the thread inherits the scheduling attributes of the creating thread or uses the scheduling attributes specified by pthread_attr_setschedparam() and pthread_attr_setschedpolicy(), and finally whether the thread has a scope of system or process.
To create a joinable thread (meaning that another thread can synchronize to its termination via pthread_join() ), you can use:
(default) pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE);
To create one that can't be joined (called a detached thread), you can use:
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
If you want the thread to inherit the scheduling attributes of the creating thread (that is, to have the same scheduling policy and the same priority), you can use:
(default) pthread_attr_setinheritsched (&attr, PTHREAD_INHERIT_SCHED);
To create one that uses the scheduling attributes specified in the attribute structure itself (which you can set using pthread_attr_setschedparam() and pthread_attr_setschedpolicy() ), you'd use:
pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED);
Finally, you never call pthread_attr_setscope() . Why? Because BlackBerry 10 OS supports only scope, and it's the default when you initialize the attribute. (System scope means that all threads in the system compete against each other for CPU; the other value, process, means that threads compete against each other for CPU within the process, and the kernel schedules the processes.)
If you do insist on calling it, you can call it only as follows:
(default) pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
The thread attribute stack parameters are prototyped as follows:
int pthread_attr_setguardsize (pthread_attr_t *attr, size_t gsize); int pthread_attr_setstackaddr (pthread_attr_t *attr, void *addr); int pthread_attr_setstacksize (pthread_attr_t *attr, size_t ssize); int pthread_attr_setstacklazy (pthread_attr_t *attr, int lazystack);
These functions all take the attribute structure as their first parameter; their second parameters are selected from the following:
The guard area is a memory area immediately after the stack that the thread can't write to. If it does (meaning that the stack was about to overflow), the thread gets hit with a SIGSEGV. If the guardsize is 0, it means that there's no guard area. This also implies that there's no stack overflow checking. If the guardsize is nonzero, then it's set to at least the system-wide default guardsize (which you can obtain with a call to sysconf() with the constant _SC_PAGESIZE). Note that the guardsize is at least as big as a (for example, 4 KB on an x86 processor). Also, note that the guard page doesn't take up any physical memory—it's done as a virtual address (MMU) trick.
The addr is the address of the stack, in case you're providing it. You can set it to NULL meaning that the system allocates (and frees!) the stack for the thread. The advantage of specifying a stack is that you can do postmortem stack depth analysis. This is accomplished by allocating a stack area, filling it with a signature (for example, the string STACK repeated over and over), and letting the thread run. When the thread has completed, you look at the stack area and see how far the thread had scribbled over your signature, giving you the maximum depth of the stack used during this particular run.
The ssize parameter specifies how big the stack is. If you provide the stack in addr, then ssize should be the size of that data area. If you don't provide the stack in addr (meaning you passed a NULL), then the ssize parameter tells the system how big a stack it should allocate for you. If you specify a 0 for ssize, the system selects the default stack size for you. Obviously, it's bad practice to specify a 0 for ssize and specify a stack using addr—effectively you're saying Here's a pointer to an object, and the object is some default size. The problem is that there's no binding between the object size and the passed value.
Finally, the lazystack parameter indicates if the physical memory should be allocated as required (use the value PTHREAD_STACK_LAZY) or all up front (use the value PTHREAD_STACK_NOTLAZY). The advantage of allocating the stack on demand (as required) is that the thread won't use up more physical memory than it absolutely has to. The disadvantage (and hence the advantage of the all up front method) is that in a low-memory environment the thread won't mysteriously die some time during operating when it needs that extra bit of stack, and there isn't any memory left. If you are using PTHREAD_STACK_NOTLAZY, you can set the actual size of the stack instead of accepting the default, because the default is quite large.
Finally, if you do specify PTHREAD_EXPLICIT_SCHED for pthread_attr_setinheritsched(), then you need a way to specify both the scheduling policy and the priority of the thread you're about to create.
This is done with the two functions:
int
pthread_attr_setschedparam (pthread_attr_t *attr,
const struct sched_param *param);
int
pthread_attr_setschedpolicy (pthread_attr_t *attr,
int policy);
The policy is simple; it's one of SCHED_FIFO, SCHED_RR, or SCHED_OTHER.
The param is a structure that contains one member of relevance here: sched_priority. Set this value via direct assignment to the desired priority.
Enough people have been caught by this that QSS has made priority zero reserved for only the idle thread. You cannot run a thread at priority zero.
Let's take a look at some examples. We assume that the proper include files (<pthread.h> and <sched.h>) has been included, and that the thread to be created is called new_thread() and is correctly prototyped and defined. The most common way of creating a thread is to let the values default:
pthread_create (NULL, NULL, new_thread, NULL);
In the above example, we created our new thread with the defaults, and passed it a NULL as its one and only parameter (that's the third NULL in the pthread_create() call above).
Generally, you can pass anything you want (via the arg field) to your new thread. Here we pass the number 123:
pthread_create (NULL, NULL, new_thread, (void *) 123);
A more complicated example is to create a non-joinable thread with round-robin scheduling at priority 15:
pthread_attr_t attr; // initialize the attribute structure pthread_attr_init (&attr); // set the detach state to "detached" pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); // override the default of INHERIT_SCHED pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy (&attr, SCHED_RR); attr.param.sched_priority = 15; // finally, create the thread pthread_create (NULL, &attr, new_thread, NULL);
To see what a multithreaded program looks like, you can run the pidin command from the shell. If our program was called spud, if we run pidin once before spud creates a thread and once after spud creates two more threads (for three total), here's what the output looks like (the pidin output is shortened to show only spud):
# pidin pid tid name prio STATE Blocked 12301 1 spud 10r READY # pidin pid tid name prio STATE Blocked 12301 1 spud 10r READY 12301 2 spud 10r READY 12301 3 spud 10r READY
As you can see, the process spud (process ID 12301) has three threads (under the tid column). The three threads are running at priority 10 with a scheduling algorithm of round robin (indicated by the r after the 10). All three threads are READY, meaning that they're able to use CPU but aren't currently running on the CPU (another, higher-priority thread, is currently running).
Now that we know all about creating threads, let's take a look at how and where we'd use them.