Thread Functions for POSIX, Solaris and Windows NT

OS/리눅스 & 유닉스 2012. 3. 14. 18:31

Thread Functions for POSIX, Solaris and Windows NT


This document describes some of the functions available for use with multithreaded applications underPOSIX , Solaris   and on Windows 9x/NT .

The POSIX thread functions are:

The POSIX semaphore functions are:

The Solaris (Sun)  thread functions are:

The 9x/NT thread functions are:

POSIX Threads

When compiling multithreaded programs, you must add the line
#include <pthread.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 -lpthread
for C++ programs
g++ -o foo foo.cpp -lpthread

pthread_create

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

#include <pthread.h>

int pthread_create(pthread_t *new_thread, const pthread_attr_t thread_attribute,
                void *(*start_routine)(void *), void *arg);
While this looks terribly confusing, you're basically specifying a function to execute within the new thread (the start_routineparameter is a function pointer) and the parameter to pass to that function (the argparameter). The function in which the thread starts should take one void* parameter and return void*.

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

The second parameter lets you specify how the thread's  should behave, but we'll just use NULL, which lets the operating system use the default values. (See the man page for the details of what else you can specify.) The thread_attribute  parameter lets you specify some details about the thread. Some valid values are: 
 

ValueMeaning
NULL Default attributes used

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 <pthread.h>

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

void main( void )
{
    pthread_t threadID;
void *exit_status;

    pthread_create(&threadID, NULL, ThreadFunc, NULL);

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

pthread_join

The pthread_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 <pthread.h>

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

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


pthread_suspend and pthread_continue


The prototypes are

#include <pthread.h>

int pthread_suspend( pthread_t target_thread );
int pthread_continue( pthread_t target_thread );
Both calls take the ID of the thread to control as the only parameter. The call to pthread_suspend immediately suspends the execution of the threadspecified by target_thread. Calling pthread_continue allows a suspended thread to continue execution.

sched_yield

The prototype is

#include <pthread.h>

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

pthread_self

Some useful functions not covered in the examples:
sched_yield();         Informs the scheduler that the thread is willing to yield its quantum, requires
no arguments.

pthread_t me;
me = pthread_self(); Allows a pthread to obtain its own identifier


POSIX Thread Synchornization

Multi-threaded applications concurrently execute instructions. Access to process-wide (or interprocess) shared resources (memory, file descriptors, etc.) requires mechanisms for coordination or synchronization among threads. The pthread library offers synchronization primitives necessary to create a deterministic application. A multi-threaded application ensures determinism by forcing asynchronous thread contexts to synchronize, or serialize, access to data structures and resources managed and manipulated during run-time. These are mutual-exclusion ( mutex ) locks, condition variables, and read-write locks. The HP-UX operating system also provides POSIX semaphores (see next section).

Mutexes furnish the means to exclusively guard data structures from concurrent modification. Their protocol precludes more than one thread which has locked the mutex from changing the contents of the protected structure until the locker performs an analogous mutex unlock. A mutex can be initialized in two ways: by a call to pthread_mutex_init(); or by assignment of PTHREAD_MUTEX_INITIALIZER .

pthread_mutex_init

The pthread_mutex_init() function initializes the mutex referenced by mutex with the attributes attr. If the parameter attr is NULL, the default mutex attributes are used. Refer to pthread_mutexattr_init(3T) for a list of default mutex attributes. After successful initialization, the mutex is initialized, unlocked, and ready to be used in mutex operations. A mutex should be initialized only once or the resulting behavior is undefined. The pthread_once() function provides a way to ensure that a mutex is initialized only once. The macro PTHREAD_MUTEX_INITIALIZER can be used to initialize mutexes that are statically allocated. These mutexes will be initialized with default attributes. The pthread_mutex_init() function does not need to be called for statically initialized mutexes. The prototype is: 
#include <pthread.h>

pthread_mutex_t mutex;
int pthread_mutex_init( pthread_mutex_t *mutex,
const pthread_mutex_attr_t *attr );

Or,
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_destroy

The pthread_mutex_destroy() function destroys the mutex referenced by mutex. This function may set mutex to an invalid value. The destroyed mutex can be reinitialized using the function pthread_mutex_init(). If the mutex is used after destruction in any mutex call, the resulting behavior is undefined. A mutex should be destroyed only when it is unlocked. Destroying a mutex that is currently being used results in undefined behavior. The prototype is: 
 int pthread_mutex_destroy(
pthread_mutex_t *mutex
);

pthread_mutex_lock

The mutex object mutex is locked by calling the pthread_mutex_lock() function. This operation returns with the mutex object referenced by mutex in the locked state with the calling thread as its owner. Deadlock detection is not provided. Attempting to relock the mutex causes deadlock. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, undefined behavior results. Attempting to recursively lock the mutex results in undefined behavior. Attempting to unlock the mutex if it was not locked by the calling thread results in undefined behavior. Attempting to unlocked the mutex if it is not locked results in undefined behavior. The prototype is: 
#include <pthread.h>

int pthread_mutex_lock(
pthread_mutex_t *mutex
);

pthread_mutex_unlock

The function pthread_mutex_unlock() is called by the owner of the mutex referenced by mutex to unlock the mutex.  Undefined behavior will result ifpthread_mutex_unlock() is called on an unlocked mutex or by a thread that is not the current owner. If there are threads blocked on the mutex referenced by mutex when pthread_mutex_unlock() releases the mutex, the scheduling policy is used to determine which thread will acquire the mutex next. The prototype is:
#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);

POSIX Semaphores

sem_init

sem_init() is used to initialize an unnamed semaphore. A successful call to sem_init() will create a new unnamed semaphore referred to by sem to the non-negative value specified by value.  If pshared is equal to 0, the unnamed semaphore is not shared with other processes. If pshared is non-zero, the unnamed semaphore is sharable with any processes that can access semTo use this function, you must link in the realtime library by specifying -lrt on the compiler or linker command line. The prototype is:
#include <sys/semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
The following call to sem_init() will create a new unnamed semaphore referred to by sem, if one does not exist, initialize the unnamed semaphore descriptor, referred to by sem, to the non-negative value specified by value.
sem_t sem;
sem_init(sem, NULL, value);

sem_post

sem_post() is used to post the semaphore referenced by sem. The calling thread will not return from its call to sem_post() unless it can either: increment the semaphore value, if there are no blocked threads on this semaphore; give the semaphore to a blocked thread, if there are any blocked threads on this semaphore; or have an error condition. If the semaphore value is < 0, the semaphore has blocked threads, waiting for it to become available (the absolute value of the semaphore's value indicates the number of waiters at that moment). If the semaphore value is >= 0, the semaphore has no waiters. If the semaphore has no waiters at the time its value is checked, the semaphore's value will be atomically incremented, with respect to the checking of its value, up to its maximum value. If the semaphore has waiters at the time its value is checked, the semaphore value is not changed. Instead, the calling thread will attempt to wake up a waiter. The prototype is 
#include <sys/semaphore.h>
int sem_post(sem_t *sem);
The following call to sem_post() will post the semaphore sem.
sem_post(sem);

sem_wait

sem_wait() is used to lock a semaphore. The calling thread will not return from its call to sem_wait() until it successfully obtains a lock on the semaphore. The value of the semaphore sem is checked at some unspecified time during the call. If the semaphore is available at the time its value is checked, the calling thread will atomically, with respect to the checking of the value, lock the semaphore. The prototype is
#include <sys/semaphore.h>

int sem_wait(sem_t *sem);
The following call to sem_wait() will lock the semaphore sem .
sem_wait(sem);


Solaris or Sun UNIX

When compiling multithreaded programs, 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

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_routineparameter is a function pointer) and the parameter to pass to that function (the argparameter). 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: 
 

ValueMeaning
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_joinreturns 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 statusis 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 bytarget_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 CreateThreadfunction 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_createfunction, it is actually doing nearly the same things. First, a note about types in Windows 9x/NT. Anything prefixed with LPis simply a pointer to something. So LPSECURITY_ATTRIBUTES is simply a pointer to a structure of type SECURITY_ATTRIBUTES . ADWORD 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 theHANDLEtype. 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. IflpThreadAttributes 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 withthr_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 WINAPIresolves to.) Note thatLPVOID(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: 
 

ValueMeaning
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 notused for this purpose in 9x/NT but isused 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 HANDLEof a thread as returned from CreateThread. The dwMilliSecondsparameter 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 HANDLEof 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).

'OS > 리눅스 & 유닉스' 카테고리의 다른 글

UNIX 명령어  (0) 2012.03.16
Using the "truss" command in Solaris  (0) 2012.03.16
sigwaitinfo  (0) 2012.02.21
pthread_kill  (0) 2012.02.16
grep 명령어  (0) 2012.02.13
: