Thread Functions


This document describes some of the functions available for use with multithreaded applications under Solaris (hawk) and on Windows 9x/NT. The Solaris functions are:

The 9x/NT functions are:

Solaris

When compiling multithreaded programs on hawk, you must add the line
#include <thread.h>
at the start of your program. In addition, you must link against the thread library. So, assuming you have a file named foo.c that you want to compile and that it uses the thread libraries, you compile it like this:
gcc -o foo foo.c -lthread
for C++ programs
g++ -o foo foo.cpp -lthread
See the document on compiling programs on hawk for more details.


thr_create

 

 

Use the thr_create function to create a new thread. The prototype is

#include <thread.h>

int thr_create( void *stack_base, size_t stack_size,
                void *(*start_routine)(void *), void *arg,
                long flags, thread_t *new_thread );
While this looks terribly confusing, you're basically specifying a function to execute within the new thread (the start_routine parameter is a function pointer) and the parameter to pass to that function (the arg parameter). The function in which the thread starts should take one void* parameter and return void*.

The first two parameters let you specify how the thread's stack should behave, but we'll just use NULL and 0 respectively, which lets the operating system use the default values. (See the man page for the details of what else you can specify.)

The last parameter (new_thread) is a pointer to a variable of type thread_t (basically a long integer) and will be filled in with the thread ID of the new thread.

The flags parameter lets you specify some details about the thread and its relationship to the process' LWPs. Some valid values are:
 

Value Meaning
0 No new LWP is added for this process
THR_NEW_LWP One new LWP is requested for the unbound threads
THR_BOUND One new LWP is requested to be bound to the thread being created. The new thread is a bound thread.
THR_SUSPENDED The new thread is created suspended and will not execute start_routine until it is started by thr_continue().

You may combine values. Below is an example that shows the function to run the thread (called ThreadFunc) and the code in main to start a new thread.

#include <thread.h>

void* ThreadFunc( void* param )
{
    printf( "My thread ID is: %d\n", thr_self() );
}

void main( void )
{
    thread_t threadID;

    thr_create( NULL, 0, ThreadFunc, NULL, 
                THR_BOUND, &threadID );

    /* wait for the thread to finish */
    thr_join( threadID, NULL, NULL );
}
thr_create returns 0 on success. Non-zero indicates an error. See the man page for details of error values.

thr_join

 

 

The thr_join function can be used by one thread to wait for another thread to finish. In this sense, it is like the wait system call that a process uses to wait for a child process to finish. The prototype is:

#include <thread.h>

int thr_join( thread_t wait_for, thread_t *departed,
              void **status );
The wait_for parameter should be the ID of the thread you wish to wait for. If the value is 0, then thr_join returns as soon as any (undetached) thread in the process terminates. If departed is not NULL, then it is filled in with the ID of the thread that terminated. If status is not NULL, then it will be filled in with the return value of the thread that terminated.

thr_join returns 0 on success. Note that only one thread can successfully join any other thread. Subsequent calls to thr_join for the terminated thread will return an error.


thr_suspend and thr_continue

 

 

The prototypes are

#include <thread.h>

int thr_suspend( thread_t target_thread );
int thr_continue( thread_t target_thread );
Both calls take the ID of the thread to control as the only parameter. The call to thr_suspend immediately suspends the execution of the thread specified by target_thread. Calling thr_continue allows a suspended thread to continue execution.



thr_yield
 

The prototype is

#include <thread.h>

int thr_yield( );
The call to thr_yield immediately suspends the execution of the current thread. This allows another thread immediate access.  At the next scheduling opportunity the thread resumes.


Windows 9x/NT

In Windows 9x/NT when you write a multithreaded application, be sure to modify the project settings so that you link using the multithreaded library. If you're just compiling from the command line, then to compile a multithreaded program in the file foo.c, you would do this:
cl -MT foo.c
The -MT option tells the compiler to use the multithreaded library. If you're creating a project in Visual C++, create a console project. Once you've done that, add your source code file and then choose Settings from the Project menu. Then go to the C/C++ tab and choose Code Generation in the Category dropdown list. Then in the Use run-time library choose Multithreaded DLL for a release compile and Debug Multithreaded DLL for a debug compile.


CreateThread
 
Use the CreateThread function to create a new thread. The prototype is
#include <windows.h>

HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes,
                     DWORD dwStackSize, 
                     LPTHREAD_START_ROUTINE lpStartAddress,
                     LPVOID lpParameter, DWORD dwCreationFlags,
                     LPDWORD lpThreadId );
While this looks even worse than the thr_create function, it is actually doing nearly the same things. First, a note about types in Windows 9x/NT. Anything prefixed with LP is simply a pointer to something. So LPSECURITY_ATTRIBUTES is simply a pointer to a structure of type SECURITY_ATTRIBUTES. A DWORD is a double word, that is, a 32-bit unsigned integer. Putting these together, we see that the LPDWORD is simply unsigned long*. Lastly, there's the HANDLE type. This is basically void*, but is used in Windows simply as an identifier. Think of it as a number.

Now, on to the parameters. The first parameter is a pointer to a structure that determines whether the returned handle can be inherited by child processes. If lpThreadAttributes is NULL, the handle cannot be inherited. Security is not implemented on Windows 9x, so this parameter is ignored there. The second parameter (dwStackSize) specifies the stack size for the new thread. A value of 0 gives it the same size as the calling thread (this is also what we did with thr_create).

The next 2 parameters specify the address of the function (lpStartAddress) to execute in the thread and the parameter (lpParameter) to pass to it. The function prototype LPTHREAD_START_ROUTINE is equivalent to a function that looks like this:

DWORD WINAPI ThreadFunc( LPVOID );
So a thread routine in 9x/NT returns a DWORD, which is simply a long integer. WINAPI specifies a particular calling convention for the function. Be sure to include it when you write your routines. (For more details, search the Visual C++ online help for __stdcall, which is what WINAPI resolves to.) Note that LPVOID (the parameter type) is simply void*, which is exactly what the thread routine in Solaris expects as well.

The last parameter (lpThreadId) takes a pointer to a variable of type DWORD (basically a long integer) and will be filled in with the thread ID of the new thread.

The dwCreationFlags parameter lets you specify some details about the thread and how it should run. Some valid values are:
 

Value Meaning
0 Use same settings as thread calling CreateThread.
THREAD_PRIORITY_LOWEST,
THREAD_PRIORITY_BELOW_NORMAL,
THREAD_PRIORITY_NORMAL,
THREAD_PRIORITY_ABOVE_NORMAL,
THREAD_PRIORITY_HIGHEST,
THREAD_PRIORITY_IDLE,
THREAD_PRIORITY_TIME_CRITICAL
These all specify thread priorities and are values that are added to the process' priority.
CREATE_SUSPENDED The new thread is created suspended and will not execute start_routine until it is started by ResumeThread().

You may combine values. Below is an example that shows the function to run the thread (called ThreadFunc) and the code in main to start a new thread.

#include <windows.h>

DWORD WINAPI ThreadFunc( void* param )
{
    printf( "My thread ID is: %d\n", GetCurrentThreadId() );
}

void main( void )
{
    DWORD  threadId;
    HANDLE hThread;
    
    hThread = CreateThread( NULL, 0, ThreadFunc, NULL, 
                            0, &threadId );

    /* wait for the thread to finish */
    WaitForSingleObject( hThread, INFINITE );

    /* clean up resources used by thread */
    CloseHandle( hThread );
}
CreateThread returns NULL on failure. When successful, the return value is used to identify the thread and operate on it. Note that the thread ID is not used for this purpose in 9x/NT but is used this way in Solaris. The call to WaitForSingleObject is one of the synchronization functions in NT that we'll be discussing later. For now we'll just consider it the equivalent of Solaris' thr_join function. The CloseHandle call is used to remove the resources associated with anything identified via a HANDLE type (of which there are many in Windows programming).


WaitForSingleObject


The WaitForSingleObject function can be used by one thread to wait for another thread to finish (later we'll also see that this function is used for synchronization of other types as well). In this sense, it is like the wait system call that a process uses to wait for a child process to finish. The prototype is:

#include <windows.h>

DWORD WaitForSingleObject( HANDLE hThread,
                           DWORD dwMilliSeconds );
The hThread parameter should be the HANDLE of a thread as returned from CreateThread. The dwMilliSeconds parameter specifies in milliseconds how long you are willing to wait for the thread to finish. You may use the predefined value of INFINITE here if you like, which of course will cause the caller to block until the specified thread does terminate, regardless of how long that takes. If you specify a timeout less than INFINITE, then you need to check the return value to see why the call returned.

If the return value is WAIT_OBJECT_0, then the thread you were waiting on terminated. If the return is WAIT_TIMEOUT, then the timeout you specified elapsed before the thread finished. WAIT_FAILED is returned if the function failed for some reason.

See the section on CreateThread for an example of usage of WaitForSingleObject. There is also a WaitForMultipleObjects function which allows you to simultaneously wait until more than one object is in a signalled state. See the online help for usage.


SuspendThread and ResumeThread


The prototypes are

#include <windows.h>

DWORD SuspendThread( HANDLE hThread );
DWORD ResumeThread( HANDLE hThread );
Both calls take the HANDLE of the thread to control as the only parameter. The call to SuspendThread immediately suspends the execution of the thread specified by hThread. Calling ResumeThread allows a suspended thread to continue execution. Both functions return 0xFFFFFFFF on error and the thread's suspend count on success (see the online help in Visual C++ for an explanation of the suspend count).