- 组成Pthreads API的库函数按照功能可以分为以下四大类:
-
- 线程管理类:由直接操作线程的库函数组成(创建、分离、接合等),除此之外还包括设置/查询线程属性的函数(属性包括可接合的、调度中的等)。
- 互斥类:用来处理线程间同步的库函数称为“互斥”,互斥函数用于对互斥操作进行创建、销毁、加锁和解锁。另外,Pthreads还提供设置/修改互斥属性的函数作为补充。
- 环境变量类:包括处理共用互斥资源的线程间通信的函数。基于特定的变量值和编程人员设定的条件,这组函数负责创建、销毁、等待和打信号。另外,Pthreads还提供设置/修改互斥有关的属性的函数作为补充。
- 同步类:包括管理(读/写)互斥锁和关卡的函数。
- 命名规则:所有的库函数都以前缀pthread_作为开始。下表列出一些常用函数类。
| 库函数前缀 | 函数类别 |
|---|---|
| pthread_ | Threads themselves and miscellaneous subroutines |
| pthread_attr_ | Thread attributes objects |
| pthread_mutex_ | Mutexes |
| pthread_mutexattr_ | Mutex attributes objects. |
| pthread_cond_ | Condition variables |
| pthread_condattr_ | Condition attributes objects |
| pthread_key_ | Thread-specific data keys |
| pthread_rwlock_ | Read/write locks |
| pthread_barrier_ | Synchronization barriers |
- 为了保证程序更好的移植性,在编写代码时对每一个用到Pthreads库的源文件都应该加上pthread.h
- 在GNU Linux环境中的编译命令:
-
- gcc -pthread
- g++ -pthread
- 函数列表
| 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。
- 一个进程允许创建的最多的线程个数是在实现时才决定的。尝试着超出最大线程数有可能会导致运行错误。
- 一旦创建,线程间都是平等的,都可以创建其他新的线程,线程间不存在隐含的层次关系或者依赖关系。如下图所示:
- 线程属性
-
- 一个线程在创建时会默认带有一定的属性。编程者可以通过线程属性对象修改某些属性。
- 函数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作为最后一句调用,主函数会进入阻塞状态,直到所有线程结束后才会结束。
- 函数pthread_create()允许编程者向线程的开始函数传递一个参数。对于那些需要很多个参数传递的情况,我们可以通过构造结构体,然后向pthread_create()传递一个指针来打破局限性。
- 在pthread_create()的形参参数的变量都必须转换为(void*)类型。
- 错误的传递参数例子:
Example 3 - Thread Argument Passing (Incorrect)
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);
...
}
|
- 相关函数
| 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()来实现。
-
- 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
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <math.h> #define NUM_THREADS 4 void *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); } |
- 函数
| 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
#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); } |
- 各种各样的函数
| 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;
本文详细介绍Pthreads API的功能及使用方法,包括线程管理、参数传递、接合与分离等核心概念。并通过实例演示如何在C语言中有效利用Pthreads进行多线程编程。
212

被折叠的 条评论
为什么被折叠?



