YoLinux Tutorial: POSIX thread

原创 2005年05月17日 15:32:00

YoLinux Tutorial: POSIX thread (pthread) libraries

The POSIX thread libraries are a standards based thread API for C/C++. It allows one to spawn a new concurrent process flow. It is most effective on multiprocessor systems where the process flow can be scheduled to run on another processor thus gaining speed through parallel or distributed processing. Threads require less overhead than "forking" or spawning a new process because the system does not initialize a new system virtual memory space and environment for the process. While most effective on a multiprocessor system, gains are also found on uniprocessor systems which exploit latency in I/O and other system functions which may halt process execution. (One thread may execute while another is waiting for I/O or some other system latency.) Parallel programming technologies such as MPI and PVM are used in a distributed computing environment while threads are limited to a single computer system. All threads within a process share the same address space. A thread is spawned by defining a function and it's arguments which will be processed in the thread. The purpose of using the POSIX thread library in your software is to execute software faster.

Contents:
Thread Basics:

  • Thread operations include thread creation, termination, synchronization (joins,blocking), scheduling, data management and process interaction.
  • A thread does not maintain a list of created threads, nor does it know the thread that created it.
  • All threads within a process share the same address space.
  • Threads in the same process share:
    • Process instructions
    • Most data
    • open files (descriptors)
    • signals and signal handlers
    • current working directory
    • User and group id
  • Each thread has a unique:
    • Thread ID
    • set of registers, stack pointer
    • stack for local variables, return addresses
    • signal mask
    • priority
    • Return value: errno
  • pthread functions return "0" if ok.


Thread Creation and Termination:

Example: pthread1.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *print_message_function( void *ptr );

main()
{
     pthread_t thread1, thread2;
     char *message1 = "Thread 1";
     char *message2 = "Thread 2";
     int  iret1, iret2;

    /* Create independant threads each of which will execute function */

     iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1);
     iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);

     /* Wait till threads are complete before main continues. Unless we  */
     /* wait we run the risk of executing an exit which will terminate   */
     /* the process and all threads before the threads have completed.   */

     pthread_join( thread1, NULL);
     pthread_join( thread2, NULL); 

     printf("Thread 1 returns: %d/n",iret1);
     printf("Thread 2 returns: %d/n",iret2);
     exit(0);
}

void *print_message_function( void *ptr )
{
     char *message;
     message = (char *) ptr;
     printf("%s /n", message);
}

Compile: cc -lpthread pthread1.c
Run: ./a.out
Results:

Details:

  • In this example the same function is used in each thread. The arguments are different. The functions need not be the same.

  • Threads terminate by explicitly calling pthread_exit, by letting the function return, or by a call to the function exit which will terminate the process including any threads.

  • Function call: pthread_create
        int pthread_create(pthread_t * thread, 
                           const pthread_attr_t * attr,
                           void * (*start_routine)(void *), 
                           void *arg);
        
    Arguments:
    • thread - returns the thread id. (unsigned long int defined in bits/pthreadtypes.h)
    • attr - Set to NULL if default thread attributes are used. (else define members of the struct pthread_attr_t defined in bits/pthreadtypes.h) Attributes include:
    • detached state (joinable? Default: PTHREAD_CREATE_JOINABLE. Other option: PTHREAD_CREATE_DETACHED)
  • scheduling policy (real-time? PTHREAD_INHERIT_SCHED,PTHREAD_EXPLICIT_SCHED,SCHED_OTHER)
scheduling parameter inheritsched attribute (Default: PTHREAD_EXPLICIT_SCHED Inherit from parent thread: PTHREAD_INHERIT_SCHED) scope (Kernel threads: PTHREAD_SCOPE_SYSTEM User threads: PTHREAD_SCOPE_PROCESS Pick one or the other not both.) guard size stack address (See unistd.h and bits/posix_opt.h _POSIX_THREAD_ATTR_STACKADDR) stack size (default minimum PTHREAD_STACK_SIZE set in pthread.h), void * (*start_routine) - pointer to the function to be threaded. Function has a single argument: pointer to void. *arg - pointer to argument of function. To pass multiple arguments, send a pointer to a structure.

Function call: pthread_exit
    void pthread_exit(void *retval);
    
Arguments:
  • retval - Return value of thread.

This routine kills the thread. The pthread_exit function never returns. If the thread is not detached, the thread id and return value may be examined from another thread by using pthread_join.
Note: the return pointer *retval, must not be of local scope otherwise it would cease to exist once the thread terminates.

[C++ pitfalls]: The above sample program will compile with the GNU C and C++ compiler g++. The following function pointer representation will work for C but not C++:
    void print_message_function( void *ptr );
    ...
    ...
    iret1 = pthread_create( &thread1, NULL, (void*)&print_message_function, (void*) message1);
    ...
    ...
    


Thread Synchronization:

The threads library provides three synchronization mechanisms:

  • mutexes - Mutual exclusion lock: Block access to variables by other threads. This enforces exclusive access by a thread to a variable or set of variables.
  • joins - Make a thread wait till others are complete (terminated).
  • condition variables - data type pthread_cond_t


Mutexes:

Mutexes are used to prevent data inconsistencies due to race conditions. A race condition often occurs when two or more threads need to perform operations on the same memory area, but the results of computations depends on the order in which these operations are performed. Mutexes are used for serializing shared resources. Anytime a global resource is accessed by more than one thread the resource should have a Mutex associated with it. One can apply a mutex to protect a segment of memory ("critical region") from other threads.

Example threaded function:

Without MutexWith Mutex
int counter=0;

/* Function C */
void functionC()
{

   counter++

}
/* Note scope of variable and mutex are the same */
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int counter=0;

/* Function C */
void functionC()
{
   pthread_mutex_lock( &mutex1 );
   counter++
   pthread_mutex_unlock( &mutex1 );
}
Possible execution sequence
Thread 1Thread 2Thread 1Thread 2
counter = 0 counter = 0 counter = 0 counter = 0
counter = 1 counter = 1 counter = 1 Thread 2 locked out.
Thread 1 has exclusive use of variable counter



counter = 2

If register load and store operations for the incrementing of variable counter occurs with unfortunate timing, it is theoretically possible to have each thread increment and overwrite the same variable with the same value. Another possibility is that thread two would first increment counter locking out thread one until complete and then thread one would increment it to 2.

SequenceThread 1Thread 2
1counter = 0counter=0
2Thread 1 locked out.
Thread 2 has exclusive use of variable counter
counter = 1
3counter = 2

Code listing: mutex1.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *functionC();
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int  counter = 0;

main()
{
   int rc1, rc2;
   pthread_t thread1, thread2;

   /* Create independant threads each of which will execute functionC */

   if( (rc1=pthread_create( &thread1, NULL, &functionC, NULL)) )
   {
      printf("Thread creation failed: %d/n", rc1);
   }

   if( (rc2=pthread_create( &thread2, NULL, &functionC, NULL)) )
   {
      printf("Thread creation failed: %d/n", rc2);
   }

   /* Wait till threads are complete before main continues. Unless we  */
   /* wait we run the risk of executing an exit which will terminate   */
   /* the process and all threads before the threads have completed.   */

   pthread_join( thread1, NULL);
   pthread_join( thread2, NULL); 

   exit(0);
}

void *functionC()
{
   pthread_mutex_lock( &mutex1 );
   counter++;
   printf("Counter value: %d/n",counter);
   pthread_mutex_unlock( &mutex1 );
}

Compile: cc -lpthread mutex1.c
Run: ./a.out
Results:

When a mutex lock is attempted against a mutex which is held by another thread, the thread is blocked until the mutex is unlocked. When a thread terminates, the mutex does not unless explicitly unlocked. Nothing happens by default.


Joins:

A join is performed when one wants to wait for a thread to finish. A thread calling routine may launch multiple threads then wait for them to finish to get the results. One wait for the completion of the threads with a join.

Sample code: join1.c

#include <stdio.h>
#include <pthread.h>

#define NTHREADS 10
void *thread_function();
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int  counter = 0;

main()
{
   pthread_t thread_id[NTHREADS];
   int i, j;

   for(i=0; i < NTHREADS; i++)
   {
      pthread_create( &thread_id[i], NULL, &thread_function, NULL );
   }

   for(j=0; j < NTHREADS; j++)
   {
      pthread_join( thread_id[j], NULL); 
   }
  
   /* Now that all threads are complete I can print the final result.     */
   /* Without the join I could be printing a value before all the threads */
   /* have been completed.                                                */

   printf("Final counter value: %d/n", counter);
}

void *thread_function()
{
   printf("Thread number %ld/n", pthread_self());
   pthread_mutex_lock( &mutex1 );
   counter++;
   pthread_mutex_unlock( &mutex1 );
}

Compile: cc -lpthread join1.c
Run: ./a.out
Results:


Condition Variables:

A condition variable is a variable of type pthread_cond_t and is used with the appropriate functions for waiting and later, process continuation. The condition variable mechanism allows threads to suspend execution and relinquish the processor until some condition is true. A condition variable must always be associated with a mutex to avoid a race condition created by one thread preparing to wait and another thread which may signal the condition before the first thread actually waits on it resulting in a deadlock. The thread will be perpetually waiting for a signal that is never sent. Any mutex can be used, there is no explicit link between the mutex and the condition variable.

Functions used in conjunction with the condition variable:

Example code: cond1.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t count_mutex     = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t condition_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  condition_cond  = PTHREAD_COND_INITIALIZER;

void *functionCount1();
void *functionCount2();
int  count = 0;
#define COUNT_DONE  10
#define COUNT_HALT1  3
#define COUNT_HALT2  6

main()
{
   pthread_t thread1, thread2;

   pthread_create( &thread1, NULL, &functionCount1, NULL);
   pthread_create( &thread2, NULL, &functionCount2, NULL);
   pthread_join( thread1, NULL);
   pthread_join( thread2, NULL);

   exit(0);
}

void *functionCount1()
{
   for(;;)
   {
      pthread_mutex_lock( &condition_mutex );
      while( count >= COUNT_HALT1 && count <= COUNT_HALT2 )
      {
         pthread_cond_wait( &condition_cond, &condition_mutex );
      }
      pthread_mutex_unlock( &condition_mutex );

      pthread_mutex_lock( &count_mutex );
      count++;
      printf("Counter value functionCount1: %d/n",count);
      pthread_mutex_unlock( &count_mutex );

      if(count >= COUNT_DONE) return(NULL);
    }
}

void *functionCount2()
{
    for(;;)
    {
       pthread_mutex_lock( &condition_mutex );
       if( count < COUNT_HALT1 || count > COUNT_HALT2 )
       {
          pthread_cond_signal( &condition_cond );
       }
       pthread_mutex_unlock( &condition_mutex );

       pthread_mutex_lock( &count_mutex );
       count++;
       printf("Counter value functionCount2: %d/n",count);
       pthread_mutex_unlock( &count_mutex );

       if(count >= COUNT_DONE) return(NULL);
    }

}

Compile: cc -lpthread cond1.c
Run: ./a.out
Results:

Note that functionCount1() was halted while count was between the values COUNT_HALT1 and COUNT_HALT2. The only thing that has been ensures is that functionCount2 will increment the count between the values COUNT_HALT1 and COUNT_HALT2. Everything else is random.

The logic conditions (the "if" and "while" statements) must be chosen to insure that the "signal" is executed if the "wait" is ever processed. Poor software logic can also lead to a deadlock condition.

Note: Race conditions abound with this example because count is used as the condition and can't be locked in the while statement without causing deadlock. I'll work on a cleaner example but it is an example of a condition variable.


Thread Scheduling:

When this option is enabled, each thread may have its own scheduling properties. Scheduling attributes may be specified:

  • during thread creation
  • by dynamically by changing the attributes of a thread already created
  • by defining the effect of a mutex on the thread's scheduling when creating a mutex
  • by dynamically changing the scheduling of a thread during synchronization operations.
The threads library provides default values that are sufficient for most cases.


Thread Pitfalls:

  • Race conditions: While the code may appear on the screen in the order you wish the code to execute, threads are scheduled by the operating system and are executed at random. It cannot be assumed that threads are executed in the order they are created. They may also execute at different speeds. When threads are executing (racing to complete) they may give unexpected results (race condition). Mutexes and joins must be utilized to acheive a predictable execution order and outcome.

  • Thread safe code: The threaded routines must call functions which are "thread safe". This means that there are no static or global variables which other threads may clobber or read assuming single threaded operation. If static or global variables are used then mutexes must be applied or the functions must be re-written to avoid the use of these variables. In C, local variables are dynamically allocated on the stack. Therefore, any function that does not use static data or other shared resources is thread-safe. Thread-unsafe functions may be used by only one thread at a time in a program and the uniqueness of the thread must be ensured. Many non-reentrant functions return a pointer to static data. This can be avoided by returning dynamically allocated data or using caller-provided storage. An example of a non-thread safe function is strtok which is also not re-entrant. The "thread safe" version is the re-entrant version strtok_r.

  • Mutex Deadlock: This condition occurs when a mutex is applied but then not "unlocked". This causes program execution to halt indefinitely. It can also be caused by poor application of mutexes or joins. Be carefull when applying two or more mutexes to a section of code. If the first pthread_mutex_lock is applied and the second pthread_mutex_lock fails due to another thread applying a mutex, the first mutex may eventually lock all other threads from accessing data including the thread which holds the second mutex. The threads may wait indefinitely for the resource to become free causing a deadlock. It is best to test and if failure occurs, free the resources and stall before retrying.

    The order of applying the mutex is also important. The following code segment illustrates a potential for deadlock:

    If function1 aquires the first mutex and function2 aquires the second, all resources are tied up and locked.

  • Condition Variable Deadlock: The logic conditions (the "if" and "while" statements) must be chosen to insure that the "signal" is executed if the "wait" is ever processed.


Thread Debugging:


Thread Man Pages:


Links:

News Groups:

  • comp.programming.threads
  • comp.unix.solaris

Programing with POSIX thread

  • 2012年03月16日 14:05
  • 19.55MB
  • 下载

POSIX thread (pthread) 库简介

今天第一次写技术内的博客,也不知道写些什么东西,就从学习Linux 下的 pthread库开始吧,本人觉得这篇文章写得不错,简单,实用,所以把它翻译一下,原文参见http://www.yolinux....
  • cr4kb0y
  • cr4kb0y
  • 2011年07月15日 23:07
  • 1253

Programing with POSIX thread.

  • 2009年02月05日 13:25
  • 10.01MB
  • 下载

posix thread介绍

 posix thread是操作系统级(OS level)的API规范,主要用来定义线程及线程间同步的相关操作,采用C语言定义。posix规范主要在unix like类系统上实现;Windows...

NPTL (NATIVE POSIX Thread Library)

POSIX Thread Library (NPTL)使Linux内核可以非常有效的运行使用POSIX线程标准写的程序。这里有一个测试数据,在32位机下,NPTL成功启动100000个线程只用了2秒,...
  • ttomqq
  • ttomqq
  • 2016年11月15日 16:13
  • 129

pthread(POSIX Thread)

phread * pthread(POSIX threads):是兼容POSIX标准操作系統的線程接口代碼,什麼是POSIX标准?-->POSIX标准是操作系統為應用程序提供的API接口國際標準,該...

一起来学POSIX thread 之 不变量、临界区、谓词

一起来学POSIX thread 之 不变量、临界区、谓词 1、不变量(invariant) 所谓的不变量是由程序作出的假设,特别是有关变量组间关系的假设。当编写队列包时,你需要为每一个队列指定一...
  • ACb0y
  • ACb0y
  • 2013年02月12日 21:11
  • 1726

POSIX thread(pthread) (一)

pthread是基于C/C++的基本线程库。 pthread可以创建一个新的工作流,在多处理器或者多核上面尤其高效,因为他可以在多个处理器上并行执行。...

POSIX thread(pthread) (三)

原文地址:http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html  线程的调度 当这个功能开启的时候,每一个线程都有他自己的调度...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:YoLinux Tutorial: POSIX thread
举报原因:
原因补充:

(最多只允许输入30个字)