POSIX thread (pthread) 库简介

来源:互联网 发布:软件企业认定证书 编辑:程序博客网 时间:2024/05/23 21:42

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

 

Pthread是一个符合POSIX标准的为C/C++ 线程库,可以利用它来创建并发处理流程的程序。 在多处理器活多核系统上使用更为有效,因为通过并行处理或分布式处理技术,可以在另外的处理器上运行生成的线程,从而使程序加速。开启一个线程相对于开启一个进程,开销小很多,因为系统无需为新线程初始化虚拟地址空间及其它资源开销。对于多处理器来说,多线程最有效果,对于单核系统,多线程也能提升程序的速度不少,因为可以利用IO或其它有可能暂停程序执行的系统调用因阻塞的延迟时间。并行编程技术,如MPI和PVM可应用于分布式计算环境,而多线程则限用于单个电脑系统。同一进程的所有线程共享一个地址空间。我们可以通过定义一个函数及其参数(在这个线程中被处理)来生成一个线程。我们在软件中使用POSIX thread 库的目的就是使程序执行得更快。

 

Thread 基础

  • 线程操作主要包括线程创建,结束,同步(join, blocking), 调度,数据管理,和进程交互(process interaction)。
  • 一个线程不会维护一个它所创建的所有线程的链表,而且也不知道是哪个线程创建了它。
  • 在一个进程中的所有线程共享同一地址空间
  • 在同一进程中的所有线程共享如下的一些资源:
    • Process instructions
    • Most data
    • open files (descriptors)
    • signals and signal handlers
    • current working directory
    • User and group id
  • 每个线程含有唯一的:
    • Thread ID
    • set of registers, stack pointer
    • stack for local variables, return addresses
    • signal mask
    • priority
    • Return value: errno
  • 如果执行成功,所有的pthread functions return "0" 

 线程的创建与结束:

Example: pthread1.c

 

  #include <stdio.h>

02#include <stdlib.h>
03#include <pthread.h>
04  
05void *print_message_function( void *ptr );
06  
07main()
08{
09     pthread_t thread1, thread2;
10     char *message1 = "Thread 1";
11     char *message2 = "Thread 2";
12     int  iret1, iret2;
13  
14    /* Create independent threads each of which will execute function */
15  
16     iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1);
17     iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);
18  
19     /*等待所有线程都结束后,主线程才能继续以完成线程的结束。 如果不这样的话,  */
20     /*主线程会继续执行,然后会调用exit(0),这会结束刚才我们创建的线程,这样我们*/
21     /*创建的两个线程有可能得不到执行就被Kill掉了。   */
22  
23     pthread_join( thread1, NULL);
24     pthread_join( thread2, NULL); 
25  
26     printf("Thread 1 returns: %d/n",iret1);
27     printf("Thread 2 returns: %d/n",iret2);
28     exit(0);
29}
30  
31void *print_message_function( void *ptr )
32{
33     char *message;
34     message = (char *) ptr;
35     printf("%s /n", message);
36}

编译:

  • C compiler: cc -lpthread pthread1.c
    or
  • C++ compiler: g++ -lpthread pthread1.c


运行: ./a.out
结果:

Thread 1Thread 2Thread 1 returns: 0Thread 2 returns: 0

细节:

  • 这个例子中,我们使用了相同的线程函数,不同的参数。其实,线程函数是可以完全不一样的。

     

  • 我们可以通过调用pthread_exit函数来明确地结束线程,也可以在线程函数中return,也可以调用exit()函数来结束进程中的所有线程。

     

  • 函数: pthread_create - 创建一个新的线程

          int pthread_create(pthread_t * thread,
                       const pthread_attr_t * attr,
                       void * (*start_routine)(void *),
                       void *arg);
      参数:

  • thread - 返回线程的 thread id. (类型:unsigned long int ,定义在bits/pthreadtypes.h)
  • attr - 如果使用默认的线程属性则传NULL。 其它情况则须为 pthread_attr_t 结构体中的成员赋值,具体成员参考bits/pthreadtypes.h),属性包括:
    • detached state (joinable?默认: PTHREAD_CREATE_JOINABLE. 其它选择: PTHREAD_CREATE_DETACHED)
    • 调度策略 (实时? PTHREAD_INHERIT_SCHED,PTHREAD_EXPLICIT_SCHED,SCHED_OTHER)
    • 调度参数 
    • inheritsched attribute (默认: PTHREAD_EXPLICIT_SCHED 从父线程继承: PTHREAD_INHERIT_SCHED)
    • 范围(Kernel 线程: PTHREAD_SCOPE_SYSTEM  用户线程: PTHREAD_SCOPE_PROCESS 只能选择它们中的一个.)
    • guard size
    • stack地址(参考 unistd.h 和 bits/posix_opt.h _POSIX_THREAD_ATTR_STACKADDR)
    • stack大小(默认最小 PTHREAD_STACK_SIZE 定义于 pthread.h),
  • void * (*start_routine) - 线程函数的函数指针. 该函数只有一个参数: void类型的指针.
  • *arg - 线程函数的参数(void 类型指针). 如果要传递多个参数, 传个指向结构体的指针.

 

  • 函数: pthread_join - 等待另外一个线程结束
        int pthread_join(pthread_t th, void **thread_return);
    参数:
    • th - 线程会暂停直到线程th 结束(线程th调用pthread_exit(),或被cancelled都会结束)才会继续执行。
    • thread_return - 如果传递的thread_return不为NULL, 线程th结束后的返回值将会保存在thread_return指针所指向的内存中.

     

  • 函数: pthread_exit - 结束调用线程
        void pthread_exit(void *retval);
    参数:
    • retval - 用于保存线程的返回值.

    这个函数用于杀掉线程. 如果这个线程备有被detached, 函数 pthread_exit 不会返回。   thread id 和返回值可被另一个使用pthread_join()的线程检测到 。
    主意:返回指针 void *retval 不能是本地作用域,因为本地作用的指针在线程结束后将并不存在。

     

  • [C++ 陷阱]: 用GNU C 和 C++编译器都可以编译上面的例子。但线面的函数指针表示只能用C编译,而不能用C++。注意它们之间微小的区别以避免产生下面的错误:
    1void print_message_function( void *ptr );
    2...
    3...
    4iret1 = pthread_create( &thread1, NULL, (void*)&print_message_function, (void*) message1);
    5...
    6...
  •  


    线程同步: threads库提供了三种线程同步的机制:

    • mutexes - Mutual exclusion 锁: 可以阻塞其它线程访问变量。用于实现线程对关键区变量的互斥访问
    • joins -使一个线程等待所有其它线程结束。.
    • 条件变量 condition variables - 数据类型pthread_cond_t

     


    Mutexes:

    当有多个线程对同一内存区域同时进行操作的时,Mutexes能用于保证数据的一致性;多线程环境中,对内存的操作的顺序如有特殊的要求时,Mutexe也能防止产生一些竞争条件。当有两个或多个线程需要对相同的内存区域进行操作的时候,争夺(内存)和竞争条件就很可能发生,但是计算的结果却由执行这些操作的顺序决定。Mutex 可以用于串行化对共享资源(如内存)的访问。如果有多个线程访问某一全局资源,都需要一个Mutex来保护该全局资源。可以用申请Mutex(Lock mutex)的方法来阻止其它线程对这段内存(关键区)的访问。Mutex只能用于同一个进程中,不像信号量(semaphores)那样能工作于多个进程。

     

    例子 :

    无Mutex有 Mutex
     
    1int counter=0
    2  
    3/* Function C */
    4void functionC()
    5{
    6  
    7   counter++
    8  
    9}
     
    01/* Note scope of variable and mutex are the same */
    02pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
    03int counter=0;
    04  
    05/* Function C */
    06void functionC()
    07{
    08   pthread_mutex_lock( &mutex1 );
    09   counter++
    10   pthread_mutex_unlock( &mutex1 );
    11}
    可能的执行顺序Thread 1Thread 2Thread 1Thread 2counter = 0counter = 0counter = 0counter = 0counter = 1counter = 1counter = 1Thread 2 被锁在外面.
    Thread 1 能独自访问variable counter   counter = 2

    如果增加变量counter的读寄存器和写寄存器操作发生在不幸的时刻,在理论上每个线程对变量counter的增加是有可能被重写为一个值的。在有mutex保护的情况下,Thread 2首先申请到mutex从而将thread 1锁在了外面,然后增加counter,此后再释放mutex,这时Thread 1 才有机会将counter增加到2。

    顺序Thread 1Thread 21counter = 0counter=02Thread 1被锁在外面.
    Thread 2 能独自访问变量countercounter = 13counter = 2 

    代码清单: mutex1.c

    01#include <stdio.h>
    02#include <stdlib.h>
    03#include <pthread.h>
    04  
    05void *functionC();
    06pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
    07int  counter = 0;
    08  
    09main()
    10{
    11   int rc1, rc2;
    12   pthread_t thread1, thread2;
    13  
    14   /* Create independent threads each of which will execute functionC */
    15  
    16   if( (rc1=pthread_create( &thread1, NULL, &functionC, NULL)) )
    17   {
    18      printf("Thread creation failed: %d/n", rc1);
    19   }
    20  
    21   if( (rc2=pthread_create( &thread2, NULL, &functionC, NULL)) )
    22   {
    23      printf("Thread creation failed: %d/n", rc2);
    24   }
    25  
    26   /* Wait till threads are complete before main continues. Unless we  */
    27   /* wait we run the risk of executing an exit which will terminate   */
    28   /* the process and all threads before the threads have completed.   */
    29  
    30   pthread_join( thread1, NULL);
    31   pthread_join( thread2, NULL); 
    32  
    33   exit(0);
    34}
    35  
    36void *functionC()
    37{
    38   pthread_mutex_lock( &mutex1 );
    39   counter++;
    40   printf("Counter value: %d/n",counter);
    41   pthread_mutex_unlock( &mutex1 );
    42}

    编译: cc -lpthread mutex1.c
    运行: ./a.out
    结果:

    Counter value: 1Counter value: 2

    当某线程试图lock一个已经被其它线程lock的mutex时,那么这个线程将会阻塞直到这个mutex被解锁。当一个线程结束,如果不显示地解锁mutex,那么被这个线程锁住的mutex并不会被自动解锁

    Man Pages:

    • pthread_mutex_lock() - 申请锁住特定的mutex变量,如果该mutex已经被另外的线程锁住,那么这个调用将会阻塞调用线程直到所申请的mutex被解锁。
    • pthread_mutex_unlock() - 解锁一个 mutex 变量. 如果其它线程拥有该Mutex变量或该mutex已经处于unlock状态,将会返回一个错误值。
    • pthread_mutex_trylock() - 试着去锁住一个mutex,如果该mutex正忙,将返回错误。这对于阻止死循环非常有用。

    Joins:

    当线程想等戴另一线程结束时,可以通过执行join来达成. 一个线程有可能启动多个线程然后等待它们结束以获得线程执行的结果。可以通过join来等待线程的完成。

    事例代码: join1.c

    01#include <stdio.h>
    02#include <pthread.h>
    04#define NTHREADS 10
    05void *thread_function(void *);
    06pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
    07int  counter = 0;
    08  
    09main()
    10{
    11   pthread_t thread_id[NTHREADS];
    12   int i, j;
    13  
    14   for(i=0; i < NTHREADS; i++)
    15   {
    16      pthread_create( &thread_id[i], NULL, thread_function, NULL );
    17   }
    18  
    19   for(j=0; j < NTHREADS; j++)
    20   {
    21      pthread_join( thread_id[j], NULL); 
    22   }
    23    
    24   /* Now that all threads are complete I can print the final result.     */
    25   /* Without the join I could be printing a value before all the threads */
    26   /* have been completed.                                                */
    27  
    28   printf("Final counter value: %d/n", counter);
    29}
    30  
    31void *thread_function(void *dummyPtr)
    32{
    33   printf("Thread number %ld/n", pthread_self());
    34   pthread_mutex_lock( &mutex1 );
    35   counter++;
    36   pthread_mutex_unlock( &mutex1 );
    37}

    编译: cc -lpthread join1.c
    运行: ./a.out
    结果:

    Thread number 1026Thread number 2051Thread number 3076Thread number 4101Thread number 5126Thread number 6151Thread number 7176Thread number 8201Thread number 9226Thread number 10251Final counter value: 10

    Man Pages:

    • pthread_create() - 创建新线程
    • pthread_join() - 等待另一线程的结束
    • pthread_self() - 返回当前线程的线程id 

    条件变量:

    条件变量的类型为pthread_cond_t,适用于某些需要等待某些条件成立后才继续执行流程的情况。条件变量机制允许线程暂停执行并放弃CPU直到某个条件成立。条件变量必须和mutex联合起来使用以避免出现这样的竞争条件,一个线程准备等待(但并未等待),另一个线程在第一个线程真正等待之前使那个条件变量受信,结果就会造成死循环,这个线程有可能永远也等不到那个条件变量受信。任何mutex都可以使用,在mutex和条件变量之间并没有明确的联系。

    与条件变量相关的函数的man pages:

    • 创建/销毁:
      • pthread_cond_init
      • pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
      • pthread_cond_destroy
    • 等待条件变量:
      • pthread_cond_wait - 解锁mutex并等待条件变量cond受信。
      • pthread_cond_timedwait - 与上面的函数类似,只是对阻塞的时间进行了限制。
    • 唤醒等待条件变量受信的线程:
      • pthread_cond_signal - 唤醒正在等待cond受信的所有线程中的一个线程。
      • pthread_cond_broadcast - 唤醒所有等待cond受信的线程。

    代码实例: cond1.c

     
    01#include <stdio.h>
    02#include <stdlib.h>
    03#include <pthread.h>
    04  
    05pthread_mutex_t count_mutex     = PTHREAD_MUTEX_INITIALIZER;
    06pthread_cond_t  condition_var   = PTHREAD_COND_INITIALIZER;
    07  
    08void *functionCount1();
    09void *functionCount2();
    10int  count = 0;
    11#define COUNT_DONE  10
    12#define COUNT_HALT1  3
    13#define COUNT_HALT2  6
    14  
    15main()
    16{
    17   pthread_t thread1, thread2;
    18  
    19   pthread_create( &thread1, NULL, &functionCount1, NULL);
    20   pthread_create( &thread2, NULL, &functionCount2, NULL);
    21  
    22   pthread_join( thread1, NULL);
    23   pthread_join( thread2, NULL);
    24  
    25   printf("Final count: %d/n",count);
    26  
    27   exit(0);
    28}
    29  
    30// Write numbers 1-3 and 8-10 as permitted by functionCount2()
    31  
    32void *functionCount1()
    33{
    34   for(;;)
    35   {
    36      // Lock mutex and then wait for signal to relase mutex
    37      pthread_mutex_lock( &count_mutex );
    38  
    39      // Wait while functionCount2() operates on count
    40      // mutex unlocked if condition varialbe in functionCount2() signaled.
    41      pthread_cond_wait( &condition_var, &count_mutex );
    42      count++;
    43      printf("Counter value functionCount1: %d/n",count);
    44  
    45      pthread_mutex_unlock( &count_mutex );
    46  
    47      if(count >= COUNT_DONE) return(NULL);
    48    }
    49}
    50  
    51// Write numbers 4-7
    52  
    53void *functionCount2()
    54{
    55    for(;;)
    56    {
    57       pthread_mutex_lock( &count_mutex );
    58  
    59       if( count < COUNT_HALT1 || count > COUNT_HALT2 )
    60       {
    61          // Condition of if statement has been met. 
    62          // Signal to free waiting thread by freeing the mutex.
    63          // Note: functionCount1() is now permitted to modify "count".
    64          pthread_cond_signal( &condition_var );
    65       }
    66       else
    67       {
    68          count++;
    69          printf("Counter value functionCount2: %d/n",count);
    70       }
    71  
    72       pthread_mutex_unlock( &count_mutex );
    73  
    74       if(count >= COUNT_DONE) return(NULL);
    75    }
    76  
    77}

    编译: cc -lpthread cond1.c
    运行: ./a.out
    结果:

    Counter value functionCount1: 1Counter value functionCount1: 2Counter value functionCount1: 3Counter value functionCount2: 4Counter value functionCount2: 5Counter value functionCount2: 6Counter value functionCount2: 7Counter value functionCount1: 8Counter value functionCount1: 9Counter value functionCount1: 10Final count: 10

    注意 functionCount1() 在变量count的值处于COUNT_HALT1与COUNT_HALT2之间的时处于停止状态。在count值处于处于COUNT_HALT1与COUNT_HALT2时,唯一能保证的事情是,functionCount2会增加变量count的值,而其它的事情都随机发生。

    逻辑条件( "if" 和 "while" 语句) 必须选择以保证在"wait"已经被处理后能发送"signal"使条件变量受信。差的软件逻辑很容易导致死循环。

    注意:本例中的竞争条件因count被用来当作条件而在while循环中不会被锁进,因此不会产生死循环。

     


    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 achieve 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 careful 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.
       
      01...
      02pthread_mutex_lock(&mutex_1);
      03while ( pthread_mutex_trylock(&mutex_2) )  /* Test if already locked   */
      04{
      05   pthread_mutex_unlock(&mutex_1);  /* Free resource to avoid deadlock */
      06   ...
      07   /* stall here   */
      08   ...
      09   pthread_mutex_lock(&mutex_1);
      10}
      11count++;
      12pthread_mutex_unlock(&mutex_1);
      13pthread_mutex_unlock(&mutex_2);
      14...

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

       
      01void *function1()
      02{
      03   ...
      04   pthread_mutex_lock(&lock1);           // Execution step 1
      05   pthread_mutex_lock(&lock2);           // Execution step 3 DEADLOCK!!!
      06   ...
      07   ...
      08   pthread_mutex_lock(&lock2);
      09   pthread_mutex_lock(&lock1);
      10   ...
      11
      12void *function2()
      13{
      14   ...
      15   pthread_mutex_lock(&lock2);           // Execution step 2
      16   pthread_mutex_lock(&lock1);
      17   ...
      18   ...
      19   pthread_mutex_lock(&lock1);
      20   pthread_mutex_lock(&lock2);
      21   ...
      22
      23main()
      24{
      25   ...
      26   pthread_create(&thread1, NULL, function1, NULL);
      27   pthread_create(&thread2, NULL, function2, NULL);
      28   ...
      29}
      If function1 acquires the first mutex and function2 acquires 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:

     

    • GDB:
      • Debugging Programs with Multiple Threads
      • GDB: Stopping and starting multi-thread programs
      • GDB/MI: Threads commands
    • DDD:
      • Examining Threads
    原创粉丝点击