Pthreads多线程编程(2)

来源:互联网 发布:淘宝家乡版怎么切换 编辑:程序博客网 时间:2024/06/06 09:59
在VS2010中配置Pthreads-win32 http://web.cs.du.edu/~sturtevant/pthread.html
本系列的学习笔记是参考的https://computing.llnl.gov/tutorials/pthreads/#Designing

三 Pthreads API介绍
  • 组成Pthreads API的库函数按照功能可以分为以下四大类:
    1. 线程管理类:由直接操作线程的库函数组成(创建、分离、接合等),除此之外还包括设置/查询线程属性的函数(属性包括可接合的、调度中的等)。
    2. 互斥类:用来处理线程间同步的库函数称为“互斥”,互斥函数用于对互斥操作进行创建、销毁、加锁和解锁。另外,Pthreads还提供设置/修改互斥属性的函数作为补充。
    3. 环境变量类:包括处理共用互斥资源的线程间通信的函数。基于特定的变量值和编程人员设定的条件,这组函数负责创建、销毁、等待和打信号。另外,Pthreads还提供设置/修改互斥有关的属性的函数作为补充。
    4. 同步类:包括管理(读/写)互斥锁和关卡的函数。
  • 命名规则:所有的库函数都以前缀pthread_作为开始。下表列出一些常用函数类。
库函数前缀函数类别pthread_Threads themselves and miscellaneous subroutinespthread_attr_Thread attributes objectspthread_mutex_Mutexespthread_mutexattr_Mutex attributes objects.pthread_cond_Condition variablespthread_condattr_Condition attributes objectspthread_key_Thread-specific data keyspthread_rwlock_Read/write lockspthread_barrier_Synchronization barriers
  • 为了保证程序更好的移植性,在编写代码时对每一个用到Pthreads库的源文件都应该加上pthread.h
四 编译线程程序
  • 在GNU Linux环境中的编译命令:
    • gcc -pthread
    • g++ -pthread
五 线程管理

     1. 创建和终止线程
  • 函数列表
pthread_create (thread,attr,start_routine,arg)//创建线程

pthread_exit (status)                         //退出线程

pthread_cancel (thread)                       //取消线程

pthread_attr_init (attr)                      //线程参数初始化

pthread_attr_destroy (attr)                   //线程参数销毁

  • 创建线程
    • main()函数在初始化的时候包含一个默认的线程,其他所有的线程都应该由编程者“显示地”创建。
    • pthread_creat创建一个新的线程并且让它可执行。这个函数(pthread_creat)可以在程序的任意地方调用任意次。
    • pthread_creat的参数:
      • thread:函数返回的创建线程唯一的标志符。
      • attr:一个不透明的属性对象,它用来设置线程属性。例如,你可以指定一个线程的属性对象,默认的值是NULL。
      • start_routine:一旦线程创建成功后,就开始执行的函数。
      • arg:传递给start_routine的单一参数,它是一个指向(void*)类型的的指针。如果没有传递任何值给arg,那么默认值是NULL。
    • 一个进程允许创建的最多的线程个数是在实现时才决定的。尝试着超出最大线程数有可能会导致运行错误。
    • 一旦创建,线程间都是平等的,都可以创建其他新的线程,线程间不存在隐含的层次关系或者依赖关系。如下图所示:

Figure 1 创建好的线程间的关系
  • 线程属性
    • 一个线程在创建时会默认带有一定的属性。编程者可以通过线程属性对象修改某些属性。
    • 函数pthread_attr_init和pthread_attr_destroy是用来初始化/销毁线程属性对象。这两个函数必须成对出现,不然会造成内存泄漏?
    • 其他函数可以查询并设置线程属性对象中的某些特定属性,包括:
      • 线程分离或者接合的状态
      • 调度继承
      • 调度策略
      • 调度参数
      • 调度竞争范围
      • 栈大小
      • 栈地址
      • 栈溢出大小
  • 线程绑定和调度
    • Pthreads API提供了一些函数来详细说明线程在执行时是怎样被调度的。例如,线程可以按照FIFO(先进先出)的形式,RR(循环)的形式和其他形式(操作系统决定的)进行调度。同时,Pthreads也提供了设置线程调度优先值的函数。
    • Pthreads API并没有提供将线程绑定在特定的CPU(或者核)的函数。然而,在具体的实现中有可能会包含这部分功能——例如提供非标准的pthread_staffinity_np库函数。注意“_np”指的是“不可移植”。
  • 终止线程&pthread_exit()
    • 有几种情况下一个线程有可能被终止:
      • 线程正常地从开始的函数返回,它的工作完成了。
      • 线程调用了pthread_exit函数,无论它的工作是否做完,都会终止当前线程。
      • 当前线程被其他线程用pthread_cancel函数进行取消。
      • 当调用exec()或者exit()后,整个进程被终止,那相应的线程也会被终止。
      • 当主函数main()结束时,并没有“显示地”调用pthread_exit函数
    • 函数pthread_exit()允许编程者指定一个可选的终止“状态”参数。这个参数会返回给那个试图“接合”当前即将“终止”的线程的线程。(在下面的例子中会有详细的介绍)
    • 对于那种运行到正常时间结束的子函数,我们一般不会调用pthread_exit()函数。当然,如果你想获得那个可选的“状态”码(言外之意,这个时候你就应该调用pthread_exit)。
    • 清扫工作:函数pthread_exit()不会自动关闭文件。在线程终止后,任何在线程里面打开的文件都会保持打开状态。那么我们在处理线程中打开的文件时就应该加倍小心,一定要记得在退出线程时关闭文件,以免造成文件读写的误操作。
    • 谈谈在主函数main()中调用pthread_exit()
      • 如果没有在主函数里面“显示地”调用pthread_exit()函数,当主函数在线程结束前结束,的确会有问题。在这种情况下,所有的由main()创建的线程都会终止。
      • 但是如果在main()函数里把“显示地”调用pthread_exit作为最后一句调用,主函数会进入阻塞状态,直到所有线程结束后才会结束。
     2. 向线程传递参数
  • 函数pthread_create()允许编程者向线程的开始函数传递一个参数。对于那些需要很多个参数传递的情况,我们可以通过构造结构体,然后向pthread_create()传递一个指针来打破局限性。
  • 在pthread_create()的形参参数的变量都必须转换为(void*)类型。
  • 错误的传递参数例子:
Example 3 - Thread Argument Passing (Incorrect)
  • This example performs argument passing incorrectly. It passes the address of variable t, which is shared memory space and visible to all threads. As the loop iterates, the value of this memory location changes, possibly before the created threads can access it.
  • 在下面例子中的主程序中,将临时变量t的地址作为pthread_create的形参传递给了创建的线程。由于变量t是存储于共享内存里的,对其他线程也可见。那么随着循环迭代增加,这块内存区域的值可能会在随后创建的线程获取之前改变,这样就会引入不确定性,导致错误。

int rc;long t;for(t=0; t<NUM_THREADS; t++) {   printf("Creating thread %ld\n", t);   rc = pthread_create(&threads[t], NULL, PrintHello, (void *) &t);   ...}
     3. 接合和分离线程
  • 相关函数
pthread_join (threadid,status)                 //接合线程ID为threadid的线程

pthread_detach (threadid)                      //分离线程ID为threadid的线程

pthread_attr_setdetachstate (attr,detachstate) //属性设置函数,设置分离状态

pthread_attr_getdetachstate (attr,detachstate) //属性获取函数,获取分离状态

  • 接合
    • “接合”是一种在线程间实现同步的方法。如下图所示,Master Thread调用pthread_create创建Worker Thread,后者进行一系列的处理,在这个过程中,Master Thread一直都在等待Worker Thread任务的完成,这种同步机制就是“接合”,在Pthreads中通过pthread_join()来实现。

Figure 2 利用pthread_join实现线程间同步
    • pthread_join()会阻塞调用此函数的线程,直到参数threadid所对应的线程(目标线程)终止。
    • 编程者可以获得目标线程的返回状态。当然前提是,在目标线程调用pthread_exit()时有status的返回状态。
    • 一个正在接合的线程只能匹配一次pthread_join()调用,试图对同一个线程匹配多个“接合”会报告逻辑错误。
  • 可接合或者不可接合
    • 当一个线程创建完毕,它其中的一个属性就是可接合/可分离的。只有在创建时声明可接合的线程在运行中才可以被接合。如果一个线程在创建时被声明为分离的,那么它必然是不可接合的。
    • POSIX标准特别说明,在创建线程时,一般来说,应该将其设置为可接合的。
    • 在显示地创建一个可接合的线程时,通常由以下几步来完成:
      • 声明一个pthread属性变量,数据类型是pthread_attr_t
      • 利用函数pthread_attr_init()对属性变量初始化
      • 利用函数pthread_attr_setdetachstate()是指属性的"分离状态"
      • 在完成这些之后,需要调用函数pthread_attr_destroy()释放库资源。
  • 分离线程
    • 函数pthread_detach()用来显示地分离某个线程,即使在创建时这个线程是可接合的。
  • 建议
    • 如果一个线程需要joining,那么建议在创建时声明为joinable,以排除并不是所有的操作系统都是将线程默认设置为joinable.
    • 如果你知道一个线程用于不会被另外一个线程join,那么尝试将它声明为detached,这样可以释放一些系统资源。
  • 示例程序(Pthread Joining)
 Example Code - Pthread Joining
  • This example demonstrates how to "wait" for thread completions by using the Pthread join routine. Since some implementations of Pthreads may not create threads in a joinable state, the threads in this example are explicitly created in a joinable state so that they can be joined later.
  • 这个例子展示了怎样通过Pthread join函数来“等待”一个线程的完成,例子中的线程在创建时都是显示地声明是为joinble状态。

#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <math.h>#define NUM_THREADS     4void *BusyWork(void *t){   int i;   long tid;   double result=0.0;   tid = (long)t;   printf("Thread %ld starting...\n",tid);   for (i=0; i<1000000; i++)   {      result = result + sin(i) * tan(i);   }   printf("Thread %ld done. Result = %e\n",tid, result);   pthread_exit((void*) t);}int main (int argc, char *argv[]){   pthread_t thread[NUM_THREADS];   pthread_attr_t attr;   int rc;   long t;   void *status;   /* Initialize and set thread detached attribute */   pthread_attr_init(&attr);   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);   for(t=0; t<NUM_THREADS; t++) {      printf("Main: creating thread %ld\n", t);      rc = pthread_create(&thread[t], &attr, BusyWork, (void *)t);       if (rc) {         printf("ERROR; return code from pthread_create()                 is %d\n", rc);         exit(-1);         }      }   /* Free attribute and wait for the other threads */   pthread_attr_destroy(&attr);   for(t=0; t<NUM_THREADS; t++) {      rc = pthread_join(thread[t], &status);      if (rc) {         printf("ERROR; return code from pthread_join()                 is %d\n", rc);         exit(-1);         }      printf("Main: completed join with thread %ld having a status               of %ld\n",t,(long)status);      } printf("Main: program completed. Exiting.\n");pthread_exit(NULL);}
     
     4. 栈管理
  • 函数
pthread_attr_getstacksize (attr, stacksize)

pthread_attr_setstacksize (attr, stacksize)

pthread_attr_getstackaddr (attr, stackaddr)

pthread_attr_setstackaddr (attr, stackaddr)

  • 预防栈有关的问题
    • POSIX标准并没有对线程的栈大小给出规定,在实现的时候栈的大小是独立且不相同的。
    • 在代码实现时,一般很容易超过默认的栈大小限制,带来的结果也是显而易见的:程序终止,数据出错。
    • 要实现安全的可移植的程序,应该“显示地”为每一个线程分配足够的栈(函数attr_setstacksize),而不是依赖于默认的栈大小限制。
    • 当某个线程的栈必须放在内存的某个特定位置时,函数pthread_attr_getstackaddr和pthread_attr_setstackaddr可以用来完成这样的任务。
  • 示例程序
 Example Code - Stack Management
  • This example demonstrates how to query and set a thread's stack size.
  • 以下示例代码说明怎样查询和设置线程的栈大小。

#include <pthread.h>#include <stdio.h>#define NTHREADS 4#define N 1000#define MEGEXTRA 1000000 pthread_attr_t attr; void *dowork(void *threadid){   double A[N][N];   int i,j;   long tid;   size_t mystacksize;   tid = (long)threadid;   pthread_attr_getstacksize (&attr, &mystacksize);   printf("Thread %ld: stack size = %li bytes \n", tid, mystacksize);   for (i=0; i<N; i++)     for (j=0; j<N; j++)      A[i][j] = ((i*j)/3.452) + (N-i);   pthread_exit(NULL);} int main(int argc, char *argv[]){   pthread_t threads[NTHREADS];   size_t stacksize;   int rc;   long t;    pthread_attr_init(&attr);   pthread_attr_getstacksize (&attr, &stacksize);   printf("Default stack size = %li\n", stacksize);   stacksize = sizeof(double)*N*N+MEGEXTRA;   printf("Amount of stack needed per thread = %li\n",stacksize);   pthread_attr_setstacksize (&attr, stacksize);   printf("Creating threads with stack size = %li bytes\n",stacksize);   for(t=0; t<NTHREADS; t++){      rc = pthread_create(&threads[t], &attr, dowork, (void *)t);      if (rc){         printf("ERROR; return code from pthread_create() is %d\n", rc);         exit(-1);      }   }   printf("Created %ld threads.\n", t);   pthread_exit(NULL);}
     5. 其他函数
  • 各种各样的函数
pthread_self ()

pthread_equal (thread1,thread2)

    • pthread_self返回一个唯一的系统分配的线程自己的thread ID。
    • pthread_equal比较两个线程的ID。如果两个ID是不相同的,返回0,否额返回一个非0值。
    • 需要注意到,线程ID是不透明的对象,C语言中的等号“==”操作符不能用于线程ID的比较。
pthread_once (once_control, init_routine)
    • 在一个进程中,pthread_once执行函数init_routine一次,对于函数init_routine的第一次调用将会以没有参数的形式执行本身函数。任何随后的调用都不起作用。
    • 简单来看,init_routine就是一个初始化的函数。
    • 参数once_control是一个同步控制的结构体,在调用pthread_once之前需要对其进行初始化,例如:pthread_once_t once_control = PTHREAD_ONCE_INIT;
原创粉丝点击