首页资源分类嵌入式开发嵌入式系统 > IBM级别的linux多线程编程手册!!!!

IBM级别的linux多线程编程手册!!!!

已有 453124个资源

下载专区

文档信息举报收藏

标    签: linux多线程编程

分    享:

文档简介

linux多线程编程,编程得参考!

文档预览

Professional Group Tec. Doc.07121901 Author-万一飞 多线程编程技术 线程是比进程更小的单位,可以认为进程是由一个或多个线程组成的。据说以前的 400 版本并不支持真正的多线程技术,在 4.2 版后才从内核上提供了对多线程的支持。总之写这 份文档的时候,绝大部分版本应该可以支持。 主要资料来源于 IBM 信息中心的《Programming Multithreaded applications》,加上部分 个人观点。 多线程编程有以下几点特殊性(说好听点叫特殊性,说得直接一点完全就是为什么不使 用多线程技术的理由,按理由的充分性,由小到大排列): 1. 多线程的编程在对 C 程序的使用上要特别小心,详细内容见调用 C 程序的注意事项。 2. 事务处理的作用范围是 JOB 级,或活动作业组级。这也就是说多线程并发时,一个线 程的 COMMIT 操作可能导致另一个线程也执行了 COMMIT 操作。(这是 IBM 说的, 不是我的猜想)。所以实际上也就可以认为多线程的并发不能支持事务操作。 3. 对于用户的应用程序来说,大部分 C、RPG、RPGLE 编写的 PGM 和 MODULE 都不具 备线程安全性,也就是不能被一个进程下的多个线程同时调用,要注意。(不排除是因 为某些参数未掌握好,总之目前测试的结果就是如此)同时也基于此,多线程之下程序 的复用性,维护性就没有单线程下那么方便和自由。 4. 最后,实际测试多线程的效率,只能用令人惊讶来形容 -- 和多进程并发效率居然几 乎是一样的!-_- (程序写起来还麻烦得要死!) 测试方法: 读一个 380 多万条记录的文件,再根据某个键值去 CHAIN 另一个 620 多万条记录 的文件,仅此而已。 根据这个 380 多万条记录的文件的某个关键字,拆分成 200 多个任务,每个任务只 处理自己需要处理的数据。(可以简单的认为每个任务是处理 380w/210 条记录吧,其实 并非如此) 多进程分为 10 个进程来处理这 200 多个任务,耗时约 3 分钟(14:40:45 – 14:43:59)。 多线程分为 8 个线程来处理这 200 多个任务,耗时竟然也是约 3 分钟(14:47:03 – 14:50:14),严格的来说,少了 3 秒钟,我估计这 3 秒就是启动 8 个线程比启动 10 个 JOB 要少的时间吧。我在多线程里还特地增加了共享了 ODP 的处理,居然还是只有这个效 果。 看到这里,如果对多线程技术还有兴趣的话,那就请继续往下看吧。我要早知道,也就 不写这么多了。 Professional Group Tec. Doc.07121901 Author-万一飞 目 录 1 概念...........................................................................................................................................6 1.1 JOB ...............................................................................................................................6 1.2 Process (进程)...............................................................................................................6 1.3 Thread (线程)................................................................................................................6 1.3.1 1.3.2 1.3.3 线程的分类 ....................................................................................................... 7 线程的程序模型 ............................................................................................... 7 JOB 和 JOB 资源..............................................................................................8 1.3.4 线程的私有数据和特有数据...........................................................................8 1.3.5 多线程编程的环境...........................................................................................9 1.4 活动作业组(Activation group)................................................................................9 1.4.1 基本资料...........................................................................................................9 1.4.2 1.4.3 1.4.4 注意事项 ......................................................................................................... 10 编译时的参数 ................................................................................................. 10 几点疑惑 ......................................................................................................... 10 1.5 调用 C 程序的注意事项 ............................................................................................11 1.6 多线程编程的通讯问题.............................................................................................11 1.7 线程的数据库、数据相关处理.................................................................................12 2 线程的基本管理操作.............................................................................................................13 2.1 线程的属性.................................................................................................................13 2.2 启动线程.....................................................................................................................14 2.3 结束线程.....................................................................................................................16 2.4 取消线程.....................................................................................................................17 2.5 挂起和重新运行线程.................................................................................................19 2.6 等待线程结束.............................................................................................................19 2.7 让进程先处理另一个线程.........................................................................................21 3 线程的安全性(Thread safety) ........................................................................................21 3.1 存储用法和线程应用.................................................................................................21 3.2 JOB 级的资源.............................................................................................................22 3.3 API 的线程安全级别 .................................................................................................23 3.4 CL 命令和线程安全...................................................................................................23 3.5 拒绝访问的函数和线程安全.....................................................................................24 3.6 退出点(exit point)..................................................................................................24 4 多线程的程序技巧.................................................................................................................25 4.1 多线程的同步.............................................................................................................25 4.1.1 CMPSWP ........................................................................................................26 4.1.2 互斥体.............................................................................................................27 4.1.3 信号量.............................................................................................................28 4.1.4 Condition variable and threads........................................................................28 4.1.5 Threads as synchronization primitives ............................................................28 4.1.6 Space location lock .........................................................................................29 Professional Group Tec. Doc.07121901 Author-万一飞 4.1.7 Object lock ......................................................................................................31 4.2 初始化和线程安全.....................................................................................................31 4.3 线程的特有数据(thread specific data) .......................................................................31 4.4 调用不具备线程安全性的函数.................................................................................34 4.5 常见的多线程错误.....................................................................................................35 5 多线程 JOB 的 DEBUG.........................................................................................................35 6 多线程 JOB 的性能................................................................................................................35 6.1 多线程服务的建议.....................................................................................................36 6.2 JOB 和线程的优先级.................................................................................................36 6.3 线程之间的冲突.........................................................................................................36 6.4 线程应用中,存储池大小设置的影响.....................................................................36 6.5 存储池的活动级别.....................................................................................................36 6.6 线程应用的性能.........................................................................................................36 7 线程管理 API Thread management API .............................................................................36 7.1 取线程属性 API -- Get Thread Attribute API ..............................................36 7.1.1 pthread_attr_getdetachstate 取 detach 状态...................................................36 7.1.2 pthread_attr_getinheritsched ...........................................................................36 7.1.3 pthread_attr_getschedparam 取线程属性计划参数......................................36 7.2 设置线程属性 API -- Set Thread Attribute API.............................................36 7.2.1 pthread_attr_init 初始化线程属性 ................................................................36 7.2.2 pthread_attr_destroy 销毁线程属性..............................................................36 7.2.3 pthread_attr_setdetachstate() 设置线程属性.................................................37 7.2.4 pthread_attr_setinheritsched............................................................................39 7.2.5 pthread_attr_setschedparam ............................................................................39 7.3 取线程内容 API -- Get Thread Content API........................................................39 7.3.1 pthread_getconcurrency 取线程的并发等级 ..............................................39 7.3.2 pthread_getpthreadoption_np..........................................................................39 7.3.3 pthread_getschedparam...................................................................................39 7.3.4 pthread_getthreadid_np 取当前线程的唯一标识号 .....................................39 7.3.5 pthread_getunique_np 取指定线程的标识号 .............................................39 7.3.6 pthread_self 取当前运行线程的线程描述符 .............................................39 7.4 设置线程内容 API -- Set Thread Content API ....................................................39 7.4.1 pthread_setconcurrency 设置进程并发等级.................................................39 7.4.2 pthread_setpthreadoption_np ..........................................................................39 7.4.3 pthread_setschedparam ...................................................................................39 7.5 检查线程 API -- Check Thread API .....................................................................40 7.5.1 pthread_equal ..................................................................................................40 7.5.2 pthread_is_initialthread_np 检查当前线程是否为初始线程 .......................40 7.5.3 pthread_is_multithreaded_np 检查当前进程是否拥有超过一个线程 ........40 7.6 线程管理 API .............................................................................................................40 7.6.1 pthread_clear_exit_np 清除线程的 EXIT 状态 ..........................................40 7.6.2 pthread_delay_np 线程 DELAY ................................................................40 7.6.3 7.6.4 pthread_detach ................................................................................................40 pthread_once 执行一次初始化 .....................................................................40 7.6.5 pthread_trace_init_np......................................................................................42 Professional Group Tec. Doc.07121901 Author-万一飞 7.6.6 PTHREAD_TRACE_NP ................................................................................42 7.6.7 sched_yield......................................................................................................42 7.7 线程操作 API -- Operation Thread API ..................................................................45 7.7.1 pthread_create() 创建线程...........................................................................45 7.7.2 pthread_exit 结束线程.................................................................................48 7.7.3 pthread_join 等待线程结束并释放线程资源...............................................50 7.7.4 pthread_join_np 等待线程结束...................................................................52 7.7.5 pthread_extendedjoin_np 根据一些扩展条件等待线程 ............................52 7.7.6 pthread_cancel 取消线程...............................................................................52 8 线程特有数据 API Thread specific storage API .................................................................54 8.1 pthread_key_create .....................................................................................................55 8.2 pthread_key_delete .....................................................................................................56 8.3 pthread_setspecific......................................................................................................57 8.4 pthread_getspecific .....................................................................................................59 9 取消线程 API Thread cancellation API.............................................................................60 9.1 pthread_cancel ............................................................................................................60 9.2 pthread_cleanup_peek_np...........................................................................................60 9.3 pthread_cleanup_pop ..................................................................................................60 9.4 pthread_cleanup_push.................................................................................................60 9.5 pthread_getcancelstate_np ..........................................................................................60 9.6 pthread_setcancelstate.................................................................................................60 9.7 pthread_setcanceltype .................................................................................................60 9.8 pthread_test_exit_np ...................................................................................................60 9.9 pthread_testcancel .......................................................................................................60 10 条件变量的 API .............................................................................................................60 10.1 pthread_cond_init........................................................................................................61 10.2 pthread_cond_wait ......................................................................................................62 10.3 pthread_cond_signal ...................................................................................................65 10.4 pthread_cond_timedwait .............................................................................................67 10.5 pthread_cond_broadcast..............................................................................................70 10.6 pthread_cond_destroy .................................................................................................70 10.7 pthread_condattr_destroy............................................................................................71 10.8 pthread_condattr_getpshared ......................................................................................71 10.9 pthread_condattr_init ..................................................................................................71 10.10 pthread_condattr_setpshared...................................................................................71 10.11 pthread_get_expiration_np......................................................................................71 11 读/写锁的同步 API Read/write lock synchronization API .........................................71 11.1 pthread_rwlock_destroy..............................................................................................71 11.2 pthread_rwlock_init ....................................................................................................71 11.3 pthread_rwlock_rdlock ...............................................................................................71 11.4 pthread_rwlock_timedrdlock_np ................................................................................71 11.5 pthread_rwlock_timedwrlock_np................................................................................71 11.6 pthread_rwlock_tryrdlock ...........................................................................................71 11.7 pthread_rwlock_trywrlock ..........................................................................................71 11.8 pthread_rwlock_unlock...............................................................................................71 Professional Group Tec. Doc.07121901 Author-万一飞 11.9 pthread_rwlock_wrlock...............................................................................................71 11.10 pthread_rwlockattr_destroy.....................................................................................72 11.11 pthread_rwlockattr_getpshared ...............................................................................72 11.12 pthread_rwlockattr_init ...........................................................................................72 11.13 pthread_rwlockattr_setpshared................................................................................72 12 其它 API -- Signal APIs............................................................................................72 12.1 pthread_kill .................................................................................................................72 12.2 pthread_sigmask..........................................................................................................72 12.3 pthread_signal_to_cancel_np ......................................................................................72 13 互斥体 API .....................................................................................................................72 13.1 互斥体操作 API -- Mutex Operation API .....................................................72 13.1.1 pthread_lock_global_np ..................................................................................73 13.1.2 pthread_unlock_global_np ..............................................................................76 13.1.3 pthread_mutex_init 互斥体初始化................................................................76 13.1.4 pthread_mutex_lock 互斥体锁 ....................................................................77 13.1.5 pthread_mutex_unlock 互斥体解锁 ............................................................79 13.1.6 pthread_mutex_destroy 销毁互斥体 ...........................................................80 13.1.7 pthread_mutex_timedlock_np (带超时设置的互斥体锁) .............................80 13.1.8 pthread_mutex_trylock (不进行阻塞处理的互斥体锁)..........................82 13.2 互斥体属性设置.....................................................................................................85 13.2.1 pthread_mutexattr_init 互斥体属性初始化 ..................................................85 13.2.2 pthread_mutexattr_destroy 销毁互斥体属性 ..............................................85 13.2.3 pthread_mutexattr_setkind_np 设置互斥体种类属性 ................................86 13.2.4 pthread_mutexattr_setname_np 设置互斥体属性名字...............................86 13.2.5 pthread_mutexattr_setpshared 设置互斥体中进程属性 .............................86 13.2.6 pthread_mutexattr_settype 设置互斥体类型属性 ......................................86 13.2.7 pthread_mutexattr_default_np 设置互斥体为默认属性.............................86 13.3 取互斥体属性 API – Mutex Attribute API........................................................86 13.3.1 pthread_mutexattr_getkind_np 取互斥体种类..............................................86 13.3.2 pthread_mutexattr_getname_np 取互斥体属性目标名 ................................86 13.3.3 pthread_mutexattr_getpshared 从互斥体中取出进程共享的属性...............86 13.3.4 pthread_mutexattr_gettype 取互斥体类型 ..................................................86 14 信号量的 API .................................................................................................................87 14.1 semget – 带 KEY 值取信号量描述符 ......................................................................87 14.2 semop 对信号量组进行操作 ..................................................................................88 14.3 semctl -- 对信号量进行控制操作.............................................................................90 15 其它 ................................................................................................................................. 92 15.1 spawn ..........................................................................................................................92 Professional Group 1 概念 Tec. Doc.07121901 Author-万一飞 1.1 JOB JOB,包含了存储和其它资源这两方面的内容,JOB 自身并不能运行。(A job is a container for storage and other resources,and it cannot run by itself) 存储,是指数据和堆栈 (Data and Stack) 其它资源, 包括环境变量、地址、文件描述、该 JOB 所打开的文件信息、当前工作目 录。 绝大部分 400 上的操作管理命令都是基于 JOB 的。 JOB 的概念我们平时接触得很多。例如说在交互式作业中,用户登录之后,即是启动了 一个 JOB,系统就需要为其分配相应的资源。我们可以通过 WRKACTJOB 命令查看活动的 作业。而由 USER、JOBNAME、JOBID 可以定位到唯一的一个 JOB,可以使用各种系统命 令,来取得指定 JOB 当前的状态(比如说 ACTIVE、MSGW、TIMW 等),或者是 JOB 的 信息(RTVJOBA),也可以使用各种系统命令来操作 JOB(HOLD,END 等)。对 JOB 的操 作、管理都很直观方便。 1.2 Process (进程) 进程,是管理程序运行的资源。(Process is container of the memory and resoures of the program)。 在 400 系统中,一个 JOB 就代表了一个进程(On IBM system i platforms, a job represent a process) 按照我的理解,启动一个 JOB,相当于系统就分配了相应的资源给这个 JOB,JOB 自 身并不负责运行程序;进程则是用来负责管理这个 JOB 下运行的程序(这里的程序应该并 不专指可以 CALL 的程序,我觉得所有的人机交互应该都是通过程序来完成的,即都是由 进程来管理的)。按照上文所说,一个 JOB 有且仅有一个进程。所以我个人认为在理解上, 似乎没有太大必要将进程与 JOB 刻意区分开来。一个进程可以对应多个运行中的程序(比 如程序 A 调用程序 B,那么当前进程就同时管理着程序 A、B 的资源。也正是基于这种原理, 所以 400 平台上的程序调用,允许有返回数据) 每个进程至少拥有一个线程(任务)来执行程序。 进程是基于 JOB 的,我们平时所说的多进程并发,实质上指的也就是多 JOB 并发。 多 JOB 并发的原理较为简单,就是使用 SBMJOB 命令提交作业来实现并发处理。通常 为了便于管理,会建立专用的子系统,以及专用的 JOBD、JOBQ(这里的专用,只是从应 用级别而言,建立的方法并没有特殊的地方),也就是说,需要启动多个 JOB。 1.3 Thread (线程) 线程是程序运行的通道,操作系统通过线程,按照一定顺序去逐步执行程序。(A thread is the path taken by a program while running, the steps performed, and the order in which the steps are performed.) 所有的程序都拥有至少一个线程,对于多线程的程序而言,每一个线程都是独立于其它的 Professional Group Tec. Doc.07121901 Author-万一飞 线程运行的。 进程中运行的第一个线程,称为初始线程(Initial thread); 并不是所有的 JOB 都支持多线程(实际上,大部分系统应用中,默认的 JOBD 的属性 都是不支持多线程)。当进程对应的 Job 支持多线程时,该进程下的其它的非初始线程的线 程,称为辅线程(Secondary thread)。 在实际操作中,我们可以通过 WRKJOB + 20,来查看线程的状态以及相关的线程信息。 1.3.1 线程的分类 线程可以分为用户线程和核心线程。 按照用户线程的分类,一个进程之下的所有程序线程都共享同一个进程线程;线程的 API 函数执行任务计划(scheduling policy),判定何时运行新的线程。任务计划决定了在一 个时点上,一个进程只能有一个活动的线程。(The scheduling policy allows only one thread to be actively running in the process at a time)。而操作系统只需要关注这个进程单一的任务 (Single Task)。 按照核心线程的分类,进程被拆分成若干个独立的任务,一个进程对应的多个核心线程 各自处理这些独立的任务。核心线程使用优先的任务计划(preemptive scheduling policy,这 里的优先,应该是相对于用户线程而言),操作系统通过这个优先的任务计划,判定当前由 哪一个核心线程来使用处理器资源。i5 操作系统支持核心线程的处理方式。 这两种分类是独立开来理解的,也就是说当前的系统平台可能只支持用户线程,而不支 持核心线程。如果不支持核心线程的话,那么一个进程之上即使并发了多个用户线程,对应 于操作系统而言,仍然只是一个单一的任务,也就是前文所说的“操作系统只需要关注的单 一的任务”(Single task);在这种情况下,表面上并发的用户线程,在操作系统层面其实仍 然是串行的(通过线程的 API 函数来分配任务),这也就是前文所说的“同一时点上,一个 进程只能有一个活动的线程”的真正含义。(这段话是我个人的理解,仅供参考) 基于系统设计时的向下兼容原理,支持核心线程的平台,也一定会支持用户线程。这时 的操作系统就同时支持了用户线程和核心线程。(事实上我想目前大部分的 400 版本都应该 是这种类型) 通常,我们使用 M*N 的方式来表达这种类型:一个进程上运行着 M 个用户线程,这 M 个用户线程共享该进程对应系统底层的 N 个核心线程。 用户线程位于核心线程的上层,可由我们用户控制的;核心线程是由系统控制的,对用 户是不可见的。系统只对更加昂贵的核心线程分配资源。用户线程到核心线程的解析由系统 来完成。 1.3.2 线程的程序模型 400 支持 call-return 的程序模型。在其它的平台中,如果程序 A 想用调用程序 B,那么 系统就必须再启动一个进程来运行这个程序 B,或者是在当前进程中用程序 B 来代替程序 A。而 400 的进程管理机制,决定了一个进程可以对应多个程序,这多个程序共享相同的进 程资源,所以可以很轻易的实现 call --return 这种方式。 这里稍微说明一下,所谓 CALL –return 模型,也就是说程序 A 调用程序 B,程序 B 的 返回值,也就是我们所说的接口数据,可以在程序 A 中直接使用。举个常见的与之相反的 例子,在 C 语言中,程序的调用就只有输入,没有返回。(函数才有返回,两个 main 函数 之前的信息传递是单向的) Professional Group Tec. Doc.07121901 Author-万一飞 启动另一个进程的需要耗费时间,以及系统资源。为了避免这种消耗,程序员们通常会 使用动态链接库(DLL)。每当程序需要使用到动态链接库中的服务时,程序就载入动态链 接库,然后调用函数,来实现程序所需要的服务。 尽管 400 的 I5 平台中,多线程程序支持 call – return 的程序模型,但是 IBM 公司还是 强烈建议我们在调用的活动作业组中使用服务程序(service program),或动态链接态。这种 建议主要是基于程序应用跨平台时的考虑。 虽然不是必须,但多线程编程所调用的程序最好是 ILE 环境下的程序,而不是 OPM 程 序(也就是不建议使用 RPG,尽量用 RPGLE)。因为 OPM 程序在多线程程序中,需要注意 到一些特殊的地方,比如说线程的安全性。 1.3.3 JOB 和 JOB 资源 前面已经提到过,JOB 包含了存储和其它资源。存储(storage)又包括了数据和堆栈 数据: 数据是指存放程序变量的地点。具体而言,可分为三类: 全局变量和静态变量(简称 static) 动态分配的存储(简称 heap)、 本地函数变量(简称 automatic) 程序变量的存储空间由活动作业组(activation group)分配,运行中的程序信息都 保存在活动作业组中。 所有运行在同一个活动作业组中的线程都可以共享使用 static 和 heap; 而当前程序的变量,以及 automatic 则由按当前线程分配使用,线程之间不做共享。 (也就是说各个线程原则上互不干扰) 堆栈: 堆栈包含线程中调用的程序流或过程流的数据(The stack contains data about the program or procedure call flow in a thread) 当建立一个线程的时候,系统分配堆栈,以及随机自动分配的存储空间。使用一个线 程的时候,堆栈以及随机自动分配的存储空间都被视为线程资源。当该线程结束时,这些 资源将会返回给进程,由进程再分配给之后启动的其它线程去使用。 一个活动作业组下的所有线程,都共享这个活动作业组中的资源,比如 static, heap(当 然,这个活动作业组中的资源也是属于 JOB 的资源)。所以当一个线程更改了这类 JOB 资 源时,其它线程查询到的,将会更改后的值。 1.3.4 线程的私有数据和特有数据 有些数据资源,虽然在程序中定义为全局变量,但线程间不能共享,而是由每个线程自 已为这些数据资源分配存储空间。这类数据称之为线程的特有数据 thread-specific data。 线程之间之间完全独立的数据,称为线程的私有数据 thread –private data. (Threads cannot share certain resources, but they can have their own view of data items called thread-specific data. Data that threads cannot share between themselves are called thread-private data.) 以下这几种资源都是属于线程的私有数据: Professional Group 线程标识符(系统唯一) 线程优先级(默认与 JOB 的相关) Security information 库列表 signal blocking mask Call stack Automatic storage 错误信息 这个是指系统自带的 errno Tec. Doc.07121901 Author-万一飞 有关线程的特有数据,详见线程的特有数据(thread specific data) 1.3.5 多线程编程的环境 交互式作业不支持多线程(还有一种通讯类的作业也不支持多线程?),所以要使用、 测试多线程必须在子系统下,比如用 SBMJOB 提交。 同时,提交到 JOB 时指定的 JOBD 要支持多线程,附带再说明一下,使用 SBMJOB 命 令时,如果不指定 JOBD,那么就默认使用当前 USER 的 JOBD。 可用 WRKJOBD 查看使用的 JOBD 的 Allow Multithread (ALWMLTTHD)这个参 数(排位比较靠后), 该参数值为 YES 时表示该 JOBD 支持多线程,为 NO 时表示不支持多线 程。该值可用 CHGJOBD 命令来更改,或在创建 JOBD 时指定。 1.4 活动作业组(Activation group) 1.4.1 基本资料 一个 JOB 下, 容纳活动中的程序、服务程序的子结构被称为活动作业组(activation group),活动作业组包含了运行程序所必须的资源,这些资源是指: static, heap 以及管理临时数据的资源 (static, heap 是简称,具体解释详见上文的 JOB 和 JOB 资源); 活动作业组的作用范围与程序编译时的参数相关,一个 JOB 就有可能对应多个活动作 业组(详见下面的注意事项)。 一个 JOB 如果支持多线程编程的话,那么这个 JOB 下的多个线程将可以共享这个 JOB 下的同一个活动作业组中的资源; 而一个线程可以运行不同的活动作业组中被激活的程序。 系统不会保存线程与活动作业组之间对应关系表,也就是说我们不知道一个活动作业组 中运行了哪些线程,也不知道一个线程对应哪些活动作业组。 所以当一个活动作业组中还有线程在运行就将其关闭的话,系统无法进行检测,只能 按照指令强制关闭该活动作业组,此时就可能会产生无法预见的错误,比如说进程非正常中 断。 为了避免这种情况,在辅线程中所有关闭活动作业组的操作,都会导致系统采用有序的 方式 end 掉 JOB(也就是说这种情况发生时,系统为了避免非正常中断,就主动正常中断)。 Professional Group Tec. Doc.07121901 Author-万一飞 所谓的有序的中断,也就是系统在 JOB 中 End 掉所有线程,然后在初始线程中调用 exit 事 务,最后关闭掉所有文件。 1.4.2 注意事项 程序编译时,如果 ACTGRP 参数是“*NEW”的话,那么每次调用这个程序,都会产 生一个新的活动作业组;当程序执行 RETURN 语句时,系统将会关闭这个新的活动作业组。 也就是说,程序如果是这样编译的话,在辅线程中程序的 Return 将会导致 JOB 的 End。 进一步解释,应用系统中,如果程序编译时,ACTGRP 参数使用了“NEW”;而在程序 结尾又使用了 Return 语句的话,那么辅线程中程序的结束将会导致整个 JOB(含初始线程) 都 End 掉,这往往与我们的预想有偏差,所以在使用时要加以注意。不过我们通常在编译 程序时都会直接使用系统的默认参数(QILE),一般也就不会出现这个错误。 还有一种情况也会导致 Job 的 End,不过我翻译不出来: If an exception has not been handled by the time it has percolated to the control boundary and the control boundary is a program entry procedure (PEP or main entry point), the multithread-capable job is ended. 然后,使用 RCLACTGRP 这个命令时(看这个名字就知道,是回收活动作业组资源的), 只允许初始线程使用,如果是辅线程使用的话,系统将会报一个 CPF180B 的错。 1.4.3 编译时的参数 如果编译程序时,ACTGRP 参数采用系统默认参数(QILE),或者是 named 的参数,那 么程序 Return 后,活动作业组仍会保留。 默认参数(QILE)是指在启动 Job 时,就产生活动作业组;在 End Job 的时候,销毁活 动作业组; Named 参数是指在 Job 中,首次调用程序时产生活动作业组;当程序 Return 之后,这 个活动作业组将会处于一个 last-used 的状态,但不会被删除掉。 1.4.4 几点疑惑 不知道编译程序时,ACTGRP 的参数又不会影响到调用程序时的版本,也就是如果不 是每次 Return 就销毁掉活动作业组,程序更新而又没有 end 掉 JOB 的时候,这个 Job 有没 有可能还是调用活动作业组所指向的原来旧版本的程序?如果旧版本的程序在内存中未清 除的话,会不会有可能产生更新了程序但仍然执行的是旧版本的问题? 如果是 C 程序,用 linkage 绑定 RPG 程序来调用的话,会不会即使编译程序时使用了 “*NEW”参数,也还是会有更新程序执行旧版本的问题? 还有,如果程序中没有 Return 语句的话, 那么是否程序结束活动作业组也不会销毁? RPG 程序在编译时没有这个参数,是不是活动作业组的概念并不针对于 RPG 程序? 很多事情都可能有关联的。 Professional Group 1.5 调用 C 程序的注意事项 Tec. Doc.07121901 Author-万一飞 C 程序的调用有一定的特殊性,系统隐含了一种可能会导致提前结束线程,或是整个 JOB 的情况。就应用级程序而言,这种情况出现的频率较上文提到的情况更为常见,所以特别在此 提示: 假设线程中最外层的程序 A,调用了其它的程序 B、C、D; 如果 B、C、D 是 C 程序,或它们自身又调用了 C 程序(这里的调用 C 程序,是指调用 main()函数),当这个被调用的 C 程序结束时,系统会有一个隐含的 pthread_exit(),或 exit(), 这个隐含的操作在当前 JOBD 支持多线程时,将会结束当前的线程;如果当前线程是初始线程, 那还将会结束当前的 JOB。如果当前 JOBD 不支持多线程,则不会这个问题。 如果 B、C、D 是 RPGLE 程序,它们没有调用 C 程序,只是调用了 C 函数,具体来说,就 是通过将 C 程序编译为 MODULE,RPGLE 程序也编译为 MODULE,然后在 RPGLE 程序中使 用 CALLB 的方式来调用 C 函数,最后使用 CRTPGM 来生成 PGM。在这种情况下,RPGLE 程 序的结束(并不是 C 函数的结束),也将会有一个隐含的 pthread_exit()或 exit(),如果当前 JOBD 支持多线程,会结束当前线程;如果当前线程是初始线程,会结束当前 JOB。如果 JOBD 不支持多线程,不会存在上述问题。 附带再说明一下 C 程序中 exit()与 return()的区别: return 是返回上层调用,exit 是结束当前程序,然后再返回上层调用。也就是 exit 有一个 结束当前程序的动作。 如果 JOBD 支持多线程,那么 C 函数中的 exit 还将会导致线程结束,无论调用 C 函数的 程序是否位于最上层调用。如果当前线程为初始线程,会结束当前 JOB。 如果 JOBD 不支持多线程,C 函数中的 exit 只会导致当前程序结束(不仅限于当前调用的 C 函数,而是当前程序),但仍会返回上层调用程序,不会结束线程。当然,如果当前程序已位 于最上层调用,那么还是会结束线程的。 无论 JOBD 是否支持多线程,如果是采用调用 C 函数而不是调用 C 程序的方式,return 的使用都只会结束当前 C 函数,返回上层调用它的程序或 Module,不会导致程序结束,也不会 导致线程结束。但是在多线程环境中,当前调用了 C 函数的程序结束,仍会导致线程的结束, 无论是否还有上层调用,参见上文描述。 也就是说,如果使用多线程技术,那么: 1、C 程序的 main()函数只允许在最外层使用,不能被任何程序调用; 2、调用了 C 函数的 RPGLE 程序,尽量位于当前线程的最外层,或是调用它的程序在调用 之后不再进行其它处理,否则会造成应用级别的异常中断; 3、如非必要,C 函数中尽量使用 return, 不使用 exit。 如果 JOBD 不支持多线程,就不会有这些问题;所以应用系统如果要从单线程转换成为多 线程,那么在 C 程序或 C 函数的调用上需要特别留意。 1.6 多线程编程的通讯问题 i5 支持的唯一的一种能保障线程安全的通讯协议,就是 socket 协议(也就是说 SNA, ODBC 之类的跨平台数据交互都不能使用多线程编程?) 在 400 上使用 socket 需要考虑到以下两点: Professional Group Tec. Doc.07121901 Author-万一飞 SOCKET API: 大部分 socket 接口都能保障线程安全,但是绝大多数网络路由器不能使用静态存 储空间。那么我们通讯时使用的函数可能需要加上_r 的后缀,比如说,原 gethostbyaddr(), 就需要改为 gethostbyaddr_r()。这类带_r 后缀的程序,与 UNIX 定义的是兼容的。所有 带_r 后缀的程序都存在于服务程序 QSOSRV2 中。 AnyNet: 在多线程程序中,AnyNet 也可以支持线程安全,但是未经测试。 1.7 线程的数据库、数据相关处理 数据库操作: 支持线程安全的数据库操作包括了:创建文件,增加 MEMBER,删除文件,删除 MEMBER。我们可以使用 DSPCMD 命令来查看当前环境下的这些命令是否支持线程安全 就用户层面来看,线程与线程间的数据操作,同样是记录锁。 比如说线程 1 执行了一个读操作,线程 2 如果也要对该记录执行操作时(operation against the same open instance),线程 2 将会等待线程 1 结束读操作。读出的结果放在 I/O 缓冲里。 如果线程操作的是不同的记录(我觉得对于普通用户而言,open instance 指的多半就是 打开文件操作某条记录的信息),那么就不需要串行。 多线程的 JOB 不支持分布式的文件(Distributed files),因为这些文件不能保障线程的 安全。 ODP 的共享: 支持多线程的的 JOB 允许共享打开文件,但是并不总是共享 ODP(Open data path) 如果文件定义了 SHARE(*YES)和 OPNSCOPE(*ACTGRPDFN),那么一个线程所 create 的子线程,如果是运行同一个活动作业组中,就可以共享 ODP; 如果文件定义了 SHARE(*YES)和 OPNSCOPE(*JOB),那么由同一个线程所 create 的 子线程都就可以共享 ODP; (那么如果我们通常定义的 SHARE 都是 NO,所以实际上同一进程下的各个线程默认就不 是共享 ODP 了?) OVRDBF: 只有初始线程能使用 OVRDBF, 辅线程使用 OVRDBF 会报错。 只有 JOB 级的,和活动作业组级的 OVRDBF 能作用于辅线程(JOB level, group level) 调用级别的 OVRDBF 对辅线程无效(Call level) 同样,DLTOVR 命令也只能在初始线程中使用。 activation 回收资源: 回收资源的命令,对于多线程来说都是不安全的,因为系统不会跟踪(track)资源,所以无 法识别资源是否仍在被线程使用。 所以 RCLRSRC、RCLACTGRP 不能在辅线程中调用,但可以在初始线程中调用。如果在 辅线程中调用,系统将会报错。 Professional Group 2 线程的基本管理操作 Tec. Doc.07121901 Author-万一飞 2.1 线程的属性 可以在启动一个线程时设置线程的属性,或在线程运行的时候更改这些属性。常见的线 程属性: 优先级 系统分配的运行时间 堆栈空间 影响到线程可以调用的函数数量 名字 我们可以根据线程的名字,来 DEBUG 或是 TRACK 这个运行中的线程 线程组 我们可以通过线程组,来管理同一时间运行的多个线程 Detach state 这个状态标识了当线程结束时,我们如何回收,或保留这个线程使用过的资源 任务计划 线程在系统或在应用中是如何被安排、计划的。 继承 判断线程的属性是否继承 更改线程的属性,可以使用系统 API 函数,如 pthread_attr_setdetachstate()函数就可以更 改 detach state 这个属性,详见 Pthread_attr_setdetachstate() 更改线程属性后,再启动的线程就将具备更改后的属性,见下例: #define _MULTI_THREADED #include #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } void *theThread(void *parm) { printf("Entered the thread\n"); return NULL; } Professional Group Tec. Doc.07121901 Author-万一飞 int main(int argc, char **argv) { pthread_attr_t attr; pthread_t thread; int rc=0; printf("Enter Testcase - %s\n", argv[0]); printf("Create a default thread attributes object\n"); rc = pthread_attr_init(&attr); checkResults("pthread_attr_init()\n", rc); printf("Set the detach state thread attribute\n"); rc = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); checkResults("pthread_attr_setdetachstate()\n", rc); printf("Create a thread using the new attributes\n"); rc = pthread_create(&thread, &attr, theThread, NULL); checkResults("pthread_create()\n", rc); printf("Destroy thread attributes object\n"); rc = pthread_attr_destroy(&attr); checkResults("pthread_attr_destroy()\n", rc); printf("Join now fails because the detach state attribute was changed\n"); rc = pthread_join(thread, NULL); if (rc==0) { printf("Unexpected results from pthread_join()\n"); exit(1); } sleep(2); printf("Main completed\n"); return 0; } 2.2 启动线程 当我们的应用程序创建了一个线程的时候,系统将会对线程的属性、控制结构、和运行 时间等内容进行初始化,以保证线程安全的运行。 当然,启动一个线程的时候,我们也需要在应用程序中对该线程可能使用到的的数据和 输入输出参数进行初始化。 启动一个线程后,系统将会为这个线程分配一个唯一的线程标识号。线程标识号是一个 整型变量,我们可以通过这个线程标识号,来对该线程进行 DEBUG,TRACE,或其它类型 Professional Group Tec. Doc.07121901 Author-万一飞 的管理操作。但是不能通过线程标识号直接操作或控制这个线程。 大部分线程的 API 函数都会返回线程描述符,我们可以通过返回的线程描述符对线程 进行直接操作,也可能通过一些同步机制等待线程结束处理。 下面的例子中,主程序启动了一个线程,并向这个线程传递了两个参数,一个是整型变 量,一个是 124 位长的字符型变量。参数做为一个全局变量来定义。 启动的线程不仅打印了主程序传递过来的参数,而且还使用了 pthread_getthreadid_np() 函数取出自身的线程标识号,注意该函数的输出是一个 pthread_id_np_t 类型的结构(其实 该结构里面也就是 hi,lo 两个整型变量) 这里主要用到的,就是 pthread_create()这个函数,函数说明详见 pthread_create() #define _MULTI_THREADED #include #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } typedef struct { int threadParm1; char threadParm2[124]; } threadParm_t; void *theThread(void *parm) { pthread_id_np_t tid; threadParm_t *p = (threadParm_t *)parm; tid = pthread_getthreadid_np(); printf("Thread ID %.8x, Parameters: %d is the answer to \"%s\"\n", tid.intId.lo, p->threadParm1, p->threadParm2); return NULL; } int main(int argc, char **argv) { pthread_t thread; int rc=0; threadParm_t *threadParm; printf("Enter Testcase - %s\n", argv[0]); Professional Group Tec. Doc.07121901 threadParm = (threadParm_t *)malloc(sizeof(threadParm)); threadParm->threadParm1 = 42; strcpy(threadParm->threadParm2, "Life, the Universe and Everything"); Author-万一飞 printf("Create/start a thread with parameters\n"); rc = pthread_create(&thread, NULL, theThread, threadParm); checkResults("pthread_create()\n", rc); printf("Wait for the thread to complete\n"); rc = pthread_join(thread, NULL); checkResults("pthread_join()\n", rc); printf("Main completed\n"); return 0; } 2.3 结束线程 结束线程通常是由该线程自身发起的。 当一个线程完成了所有的处理之后,它将会有一个关闭自身的动作,释放系统资源以便 之后其它的线程使用这些资源。 有些 API 函数要求应用程序在程序结束时,明确地给出释放资源的语句。也有些线程 的处理机制没有这样要求(如 JAVA)。 可以有多种方法去结束一个线程。最好的方法就是 return 到创建这个线程的程序中。因 为有关线程的 API 函数。 有些 API 函数也支持 exception 机制。这里所说的 Exception 机制,是指当发生一个一 个 exception 而且没有去处理它的时候,线程将会结束。 下面的例子中,主要是在线程调用的函数中使用的 pthread_exit()函数来结束掉辅线程, 以及初始线程中使用的 pthread_join()接收辅线程中的返回。 Pthread_exit 函数的说明详见 pthread_exit 结束线程 Pthread_join 函数的说明详见 pthread_join #define _MULTI_THREADED #include #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ Professional Group } Tec. Doc.07121901 Author-万一飞 const int THREADFAIL = 1; const int THREADPASS = 0; void *theThread(void *parm) { printf("Thread: End with success\n"); pthread_exit(__VOID(THREADPASS)); printf("Thread: Did not expect to get here!\n"); return __VOID(THREADFAIL); } int main(int argc, char **argv) { pthread_t thread; int rc=0; void *status; printf("Enter Testcase - %s\n", argv[0]); printf("Create/start a thread\n"); rc = pthread_create(&thread, NULL, theThread, NULL); checkResults("pthread_create()\n", rc); printf("Wait for the thread to complete, and release its resources\n"); rc = pthread_join(thread, &status); checkResults("pthread_join()\n", rc); printf("Check the thread status\n"); if (__INT(status) != THREADPASS) { printf("The thread failed\n"); } printf("Main completed\n"); return 0; } 2.4 取消线程 取消线程通常不是由该程序自身发起的,而是由其它的线程发起。 取消线程的时候要注意,如果我们应用程序中,对于清除数据与解锁的处理机制不合理 时,将可能会破坏数据,或造成应用程序死锁。 在下面这个例子中,子线程所调用的函数每隔一秒钟打印一行,主程序在创建子线程 3 Professional Group Tec. Doc.07121901 Author-万一飞 秒钟后,发出取消该子线程的指令。 主要使用的函数为 pthread_cancel(),API 函数说明见 pthread_cancel 取消线程 注意到使用了 pthread_cancel 之后,仍然要使用 pthread_join 函数等待子线程结束; 如 果 子 线 程 被 成 功 取 消 , 那 么 pthread_join 函 数 取 到 的 状 态 将 为 PTHREAD_CANCELED。 #define _MULTI_THREADED #include #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } void *theThread(void *parm) { printf("Thread: Entered\n"); while (1) { printf("Thread: Looping or long running request\n"); pthread_testcancel(); sleep(1); } return NULL; } int main(int argc, char **argv) { pthread_t thread; int rc=0; void *status; printf("Enter Testcase - %s\n", argv[0]); printf("Create/start a thread\n"); rc = pthread_create(&thread, NULL, theThread, NULL); checkResults("pthread_create()\n", rc); printf("Wait a bit until we 'realize' the thread needs to be canceled\n"); sleep(3); rc = pthread_cancel(thread); checkResults("pthread_cancel()\n", rc); Professional Group Tec. Doc.07121901 Author-万一飞 printf("Wait for the thread to complete, and release its resources\n"); rc = pthread_join(thread, &status); checkResults("pthread_join()\n", rc); printf("Thread status indicates it was canceled\n"); if (status != PTHREAD_CANCELED) { printf("Unexpected thread status\n"); } printf("Main completed\n"); return 0; } 2.5 挂起和重新运行线程 有时我们需要暂时停止线程的运行。当我们挂起一个线程时,这个线程的状态,以及线 程属性、锁住的记录, 都将维持现状,直至线程重新开始运行(resume)。 挂起线程时要小心,因为这可能会导致应用程序死锁,或超时。我们可以使用其它更安 全的方式来解决大部分问题,包括挂起线程。(比如说同步机制) 挂起线程后,我们需要在应用程序中重新启用这个线程,重新启用后,线程将从挂起点 继续开始运行。 2.6 等待线程结束 当我们使用线程的时候,知道线程何时结束是很重要的。等待线程执行一个操作,或等 待线程发生一个事件,称之为同步机制。 常见的等待,就是等待至线程结束。当线程结束的时候,应用程序将会被提示线程分配 的工作已完成,或线程运行失败。我们可以通过 API 函数中设置的参数来确认线程运行的 成功与否。 在大型的应用程序中,等待一组线程结束可能是一种比较好的方式。比如说通过调用程 序 在下面这个例子中,主程序启动了多个子线程,并等待这些子线程结束,然后检查子线 程的结束状态。 在这个例子中,等待一个线程结束使用到的函数仍然是 pthread_join. #define _MULTI_THREADED #include #include #include #include #define THREADGROUPSIZE 5 Professional Group Tec. Doc.07121901 Author-万一飞 #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } void *theThread(void *parm) { printf("Thread %.8x %.8x: Entered\n", pthread_getthreadid_np()); printf("Thread %.8x %.8x: Working\n", pthread_getthreadid_np()); sleep(15); printf("Thread %.8x %.8x: Done with work\n", pthread_getthreadid_np()); return NULL; } int main(int argc, char **argv) { pthread_t thread[THREADGROUPSIZE]; void *status[THREADGROUPSIZE]; int i; int rc=0; printf("Enter Testcase - %s\n", argv[0]); printf("Create/start some worker threads\n"); for (i=0; i #define _MULTI_THREADED #include #include #include #include #define ATOMICADD(var, val) { \ int aatemp1 = (var); \ int aatemp2 = aatemp1 + val; \ while( ! _CMPSWP( &aatemp1, &var, aatemp2 ) ) \ aatemp2 = aatemp2 + val; \ } Professional Group #define NUMTHREADS 10 #define LOOPCONSTANT 100000 Tec. Doc.07121901 Author-万一飞 int shareData=0; void *theThread(void *parm) { int loop; printf("Thread %.8x, %.8x is Entered\n", pthread_getthreadid_np()); for(loop=0;loop #include #include #include #include /* Lock types */ #include /* LOCKSL instruction */ #include /* UNLOCKSL instruction */ #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } #define int int NUMTHREADS 3 sharedData=0; sharedData2=0; Professional Group Tec. Doc.07121901 Author-万一飞 void *theThread(void *parm) { int rc; printf("Thread %.8x %.8x: Entered\n", pthread_getthreadid_np()); locksl(&sharedData, _LENR_LOCK); /* Lock Exclusive, No Read */ /********** Critical Section *******************/ printf("Thread %.8x %.8x: Start critical section, holding lock\n", pthread_getthreadid_np()); /* Access to shared data goes here */ ++sharedData; --sharedData2; printf("Thread %.8x %.8x: End critical section, release lock\n", pthread_getthreadid_np()); unlocksl(&sharedData, _LENR_LOCK); /* Unlock Exclusive, No Read */ /********** Critical Section *******************/ return NULL; } int main(int argc, char **argv) { pthread_t thread[NUMTHREADS]; int rc=0; int i; printf("Enter Testcase - %s\n", argv[0]); printf("Hold Lock to prevent access to shared data\n"); locksl(&sharedData, _LENR_LOCK); /* Lock Exclusive, No Read */ printf("Create/start threads\n"); for (i=0; i #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } #define NUMTHREADS 3 pthread_key_t tlsKey = 0; typedef struct { int data1; int data2; } mystruct; void globalDestructor(void *value) { printf("%.8x,%.8x: In the data destructor\n", \ pthread_getthreadid_np()); free(value); pthread_setspecific(tlsKey, NULL); } void showdata() { mystruct *gdata; gdata=pthread_getspecific(tlsKey); printf("%.8x, %.8x: get data data1= %d, data2=%d\n", \ pthread_getthreadid_np(), gdata->data1, gdata->data2); } Professional Group Tec. Doc.07121901 Author-万一飞 void *threadfunc(void *parm) { mystruct *gdata; gdata=(mystruct *)parm; printf("%.8x, %.8x: set data data1= %d, data2=%d\n", \ pthread_getthreadid_np(), gdata->data1, gdata->data2); pthread_setspecific(tlsKey, gdata); showdata(); printf("%.8x, %.8x: Ready exit thread, run function destruct\n", pthread_getthreadid_np()); return NULL; } int main(int argc, char **argv) { pthread_t thread[NUMTHREADS]; int rc=0; int i=0; mystruct *gData; printf("Enter Testcase - %s\n", argv[0]); printf("Create a thread local storage key\n"); rc = pthread_key_create(&tlsKey, globalDestructor); checkResults("pthread_key_create()\n", rc); /* The key can now be used from all threads */ for(i=0;idata1=i; gData->data2=(i+1)*i; pthread_create(&thread[i], NULL, threadfunc, gData); } for(i=0;i #include int main(int argc, char *argv[]) { if (Qp0zSystem("CRTLIB LIB(XYZ)") != 0) printf("Error creating library XYZ.\n"); else printf("Library XYZ created.\n"); return(0); } 2、 如果应用程序中调用了不具备安全性的 API 或程序,可以使用 spawn()函数来 启一个 JOB 去运行。Spawn()函数可以继承原线程中的资源,比如 IFS 文件, socket 描述符。Spawn 的例子可见 spawn 3、 如果应用程序频繁的调用了不具备安全性的多个函数,那么可以考虑通过上述 方法,启动一个新 JOB,专门去运行这些函数。JOB 之间可以通过消息队列, 数据队列进行通讯。 Professional Group 4.5 常见的多线程错误 Tec. Doc.07121901 Author-万一飞 在多线程编程中,常见的错误有以下几种: 调用不具备线程安全性的函数 这几乎是最常见的错误。在应用程序之中,需要确保它调用的每一个 API 函数都 具备线程的安全性。 当前 JOB 不允许创建多线程 要注意 JOBD 中 ALWMLTTHD 的值,为*YES 时才可以。 交互式作业不支持多线程。 如果当前 JOB 对应 JOBD 不支持多线程,那么将无法运行多线程程序 关闭活动作业组 进程下的一个活动作业组,可能对应多个线程,系统无法安全的关闭活动作业组, 所以当线程执行了关闭活动作业组的动作时(比如说 C 程序中的 exit(),abort()),系统 将会结束掉整个进程。 在前面的活动作业组,以及调用 C 程序的注意事项中,已就这个问题进行了应用 层的表述。 混合使用线程 API IBM 要我们不要把 pthread 的 API 和系统提供的其它线程管理的 API 混用,比如说 JAVA。 事务处理 事务处理是 JOB 级,或活动作业组级的。因为我们无法知道,也无法控制线程运 行与活动作业组的对应关系,于是事实上,事务处理就不能针对单个线程了。 如果同时有多个线程在进行数据库操作,那一个一个线程的 commit 操作,可能会 导致另一个活动中的线程也执行了 commit 操作。 于是多线程编程,在实际上就不支持事务处理。 5 多线程 JOB 的 DEBUG 居然没有调试成功,失败,略。 6 多线程 JOB 的性能 网上有很多这方面的资料,所以略。 Professional Group 6.1 多线程服务的建议 Tec. Doc.07121901 Author-万一飞 6.2 JOB 和线程的优先级 6.3 线程之间的冲突 6.4 线程应用中,存储池大小设置的影响 6.5 存储池的活动级别 6.6 线程应用的性能 7 线程管理 API Thread management API 7.1 取线程属性 API -API Get Thread Attribute 7.1.1 pthread_attr_getdetachstate 取 detach 状态 7.1.2 pthread_attr_getinheritsched 7.1.3 pthread_attr_getschedparam 取 线 程 属 性 计 划参数 7.2 设 置 线 程 属 性 API -- Set Thread Attribute API 7.2.1 pthread_attr_init 初始化线程属性 7.2.2 pthread_attr_destroy 销毁线程属性 Professional Group Tec. Doc.07121901 Author-万一飞 7.2.3 pthread_attr_setdetachstate() 设置线程属性 语法: #include int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 功能: 设置线程属性中的 detach 状态(detach state),这个状态标识了当一个线程结束时,系 统是否会释放线程的资源。这里,“线程结束”这个用语,包括但不仅限于线程的正常退出 (即也包括异常中断)。另外,有部分资源(如 automatic storage,具体含义在 JOB 和 JOB 资源中),是当线程结束时总是会被释放的。 Detach 状态的值必须在下面两个中选择其一: PTHREAD_CREATE_DETACHED //就是 0, 表示释放资源? PTHREAD_CREATE_JOINABLE //就是 1, 表示不释放资源? 系统的默认状态值是 PTHREAD_CREATE_JOINABLE 参数: attr (输入参数) 标识线程属性结构的地址 detachstate (输入参数) 标识修改 detach state 状态的值,必须为 PTHREAD_CREATE_DETACHED 或 PTHREAD_CREATE_JOINABLE 返回: 0 表示成功 非 0 表示失败 简单举例: #define _MULTI_THREADED #include #include int main(int argc, char **argv) { int rc=0; int detachstate; pthread_attr_t pta; rc = pthread_attr_init(&pta); rc = pthread_attr_getdetachstate(&pta, &detachstate); printf("detach state = %d\n", detachstate); rc = pthread_attr_setdetachstate(&pta, PTHREAD_CREATE_DETACHED); rc = pthread_attr_getdetachstate(&pta, &detachstate); Professional Group printf("detach state = %d\n", detachstate); Tec. Doc.07121901 Author-万一飞 return 0; } 这个例子就是先对线程属性进行初始化,然后更改 detach state。 为了标识出更改前后的变化,还使用到了 pthread_attr_getdetachstate 这个函数取出当前 detach state,以示区分 Professional Group 7.2.4 Tec. Doc.07121901 Author-万一飞 pthread_attr_setinheritsched 7.2.5 pthread_attr_setschedparam 7.3 取线程内容 API -- Get Thread Content API 7.3.1 pthread_getconcurrency 取线程的并发等级 7.3.2 pthread_getpthreadoption_np 7.3.3 pthread_getschedparam 7.3.4 pthread_getthreadid_np 取 当 前 线 程 的 唯 一 标识号 7.3.5 pthread_getunique_np 取指定线程的标识号 7.3.6 pthread_self 符 取当前运行线程的线程描述 7.4 设置线程内容 API -- Set Thread Content API 7.4.1 pthread_setconcurrency 设置进程并发等级 7.4.2 pthread_setpthreadoption_np 7.4.3 pthread_setschedparam Professional Group Tec. Doc.07121901 Author-万一飞 7.5 检查线程 API -- Check Thread API 7.5.1 pthread_equal 7.5.2 pthread_is_initialthread_np 检查当前线程是 否为初始线程 7.5.3 pthread_is_multithreaded_np 检 查 当 前 进 程 是否拥有超过一个线程 7.6 线程管理 API 7.6.1 pthread_clear_exit_np 清除线程的 EXIT 状 态 7.6.2 pthread_delay_np 线程 DELAY 7.6.3 pthread_detach 7.6.4 pthread_once 执行一次初始化 语法: #include int pthread_once(pthread_once_t *once_control, 功能: void (*init_routine)(void)); 这个函数针对指定的变量 once_control,只执行一次初始化。当多个线程先后执行了同 样的 pthread_once 语句时,初始化函数 init_routine 只会执行一次。 初始化函数 init_routine 必须进行如下定义: void initRoutine(void); 参数: Professional Group Tec. Doc.07121901 Author-万一飞 once_control 输入参数,分配给初始化事件的控制变量。如果有不同初始化事件(即需要调用不 同的初始化函数),那么需要定义不同的控制变量。该变量是一个 pthread_once_t 类型 的结构体 init_route 输入参数,初始化函数的指针,这个函数没有入口参数,也没有返回. 返回: 0 – 成功; 非 0 -- 失败 例子: 下面这个例子,就充分体现了 pthread_once 的用法。 初始线程创建了三个子线程,这三个子线程都使用了 pthread_once 语句,但 pthread_once 语句中使用的初始化函数 initRoutine 就只运行了一次(即只会打印一次“In the initRouinte”), 而且 number 的值也只为 1。 #define _MULTI_THREADED #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } #define int int pthread_once_t NUMTHREADS 3 number = 0; okStatus = 777; onceControl = PTHREAD_ONCE_INIT; void initRoutine(void) { printf("In the initRoutine\n"); number++; } void *threadfunc(void *parm) { printf("Inside secondary thread\n"); pthread_once(&onceControl, initRoutine); return __VOID(okStatus); } int main(int argc, char **argv) { pthread_t thread[NUMTHREADS]; Professional Group int int void rc=0; i=NUMTHREADS; *status; Tec. Doc.07121901 Author-万一飞 printf("Enter Testcase - %s\n", argv[0]); for (i=0; i < NUMTHREADS; ++i) { printf("Create thread %d\n", i); rc = pthread_create(&thread[i], NULL, threadfunc, NULL); checkResults("pthread_create()\n", rc); } for (i=0; i < NUMTHREADS; ++i) { printf("Wait for thread %d\n", i); rc = pthread_join(thread[i], &status); checkResults("pthread_join()\n", rc); if (__INT(status) != okStatus) { printf("Secondary thread failed\n"); exit(1); } } if (number != 1) { printf("An incorrect number of 1 one-time init routine was called!\n"); exit(1); } printf("One-time init routine called exactly once\n"); printf("Main completed\n"); return 0; } 7.6.5 pthread_trace_init_np 7.6.6 PTHREAD_TRACE_NP 7.6.7 sched_yield 语法: #include int sched_yield(void); Professional Group Tec. Doc.07121901 Author-万一飞 功能: 这个函数可以使用另一个级别等于或高于当前线程的线程先运行。 如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序。 参数: 无 返回: 0 – 成功; 非 0 – 失败 例子: 下面这个例子中,只是使用了 sched_yield 这个函数,其实就实际效果上,并未体现出 其真正的意义,主要旨在体会用法。 #define _MULTI_THREADED #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } #define #define LOOPCONSTANT 1000 THREADS 3 pthread_mutex_t int mutex = PTHREAD_MUTEX_INITIALIZER; i,j,k,l; void *threadfunc(void *parm) { int loop = 0; int localProcessingCompleted = 0; int numberOfLocalProcessingBursts = 0; int processingCompletedThisBurst = 0; int rc; printf("Entered secondary thread\n"); for (loop=0; loop int pthread_create( pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); 功能: 按指定的属性创建一个线程。(即设置线程属性之类的 API 函数应在创建线程之前就调 用),同时会在线程中运行指定的线程函数,主程序与线程函数之间可以通过一个指针来传 递参数。当 pthread_create()成功结束时,返回的线程描述符将会保存下来,用来指向该线程 (以便程序的后续处理,例如主程序就可以通过这个返回线程描述符对线程进行操作)。 当线程函数正常返回时,系统隐含地调用了函数 pthread_exit()。 创建的线程有可能(但并不一定)在 pthread_create 函数返回前就开始运行了。 如果线程属性的值被更改的话,之前已创建的线程并不受影响。 如果不特别指定线程属性的话将会使用默认的线程属性。 也就是说,假如我们在程序段中已进行如下定义: pthread_t t; void *foo(void *); pthread_attr_t attr; pthread_attr_init(&pta); 如果中间不做其它的处理,那么下面这两句话是等价的: pthread_create(&t, NULL, foo, NULL); pthread_create(&t, &attr, foo, NULL); 新创建的线程中,cancellation state 的值是 PTHREAD_CANCEL_ENABLE。 Cancellation type 的值是 PTHREAD_CANCEL_DEFERRED 初始线程是特殊的,任何初始线程的结束(如使用 pthread_exit(),或其它结束初始线程 的操作),都会导致整个进程的结束。 也就是说,如果初始线程启动了子线程,如果不做任何其它操作就结束初始线程的话, 那么所有的子线程都会立刻结束。 系统并没有限制一个进程中可以启动的线程最大数。在实际使用中,线程数量的限制决 定于 JOB 中可用的存储空间。 Professional Group Tec. Doc.07121901 Author-万一飞 在创建线程之后,最后总是使用 pthread_join()或 pthread_detach()函数,使用这两个函数 将可以使得线程结束时,资源被回收。 参数: thread (输出参数) 创建的线程的描述符,可以通过该描述符来操作这个建立的线程 attr (输入参数) 表明创建线程的属性,如果使用 NULL,则表示使用默认的线程属性。 Start_routine 输入参数 创建的线程中调用的函数 arg 输入参数 主线程与创建的子线程之间传递参数的地址 返回: 0 表示成功 非 0 表示失败 例子: 在下面这个例子中,主程序创建了两个线程,这两个线程均调用函数 threadfunc(); 第一次创建的方式,是使用 NULL 方式指定使用默认的线程属性; 第二次创建,是通过一个 pthread_attr_t 结构的变量 pta,来指定使用线程属性。因为主 程序中进行了线程属性初始化之后,没有再更改线程属性,所以这两种创建方式实质上都是 使用了默认的线程属性。 创建了两个线程之后,再将线程属性的目标 destory。根据上文所说,主程序中线程属 性的变更不会影响到已创建的线程。所以这里的 destory 对刚才已创建的线程没有影响。 主程序与线程之间可以通过一个指针来传递参数,本程序中指针对应的参数设为一个结 构体变量,结构体中含有一个整型变量,和一个 128 位长的字符变量。我们自己写的程序可 以参照这种方式来传递多个变量。 程序打印出来的结果,有可能是: Create a thread attributes object Create thread using the NULL attributes Create thread using the default attributes Destroy thread attributes object Inside secondary thread, parm = 5 Inside secondary thread, parm = 77 Main completed 或 Create a thread attributes object Create thread using the NULL attributes Create thread using the default attributes Inside secondary thread, parm = 5 Destroy thread attributes object Inside secondary thread, parm = 77 Main completed 注意到“Destroy thread attributes object”这句话有可能在第二个线程运行之前开始执行, Professional Group Tec. Doc.07121901 Author-万一飞 也有可能在第一个线程运行之前就开始执行,这是由系统去分配资源执行的,我们无法控制。 如果把 sleep(5)这句话去掉的话,那么线程执行函数 threadfunc()中的打印语句,本来应该打 印两句,就有可能一句都没有打印或是只打印一句出来。因为主程序(初始线程)的结束, 会导致整个进程的结束。当然,使用 sleep 这种等待方式只是用于测试,既不安全,又没有 效率。在实际应用中,我们会使用其它更有效率,更安全的语句来等待辅线程的结束,比如 pthread_join(). #define _MULTI_THREADED #include #include #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } typedef struct { int value; char string[128]; } thread_parm_t; void *threadfunc(void *parm) { thread_parm_t *p = (thread_parm_t *)parm; printf("%s, parm = %d\n", p->string, p->value); free(p); return NULL; } int main(int argc, char **argv) { pthread_t thread; int rc=0; pthread_attr_t pta; thread_parm_t *parm=NULL; /************************************************************/ /* 线程属性初始化 */ printf("Create a thread attributes object\n"); rc = pthread_attr_init(&pta); Professional Group checkResults("pthread_attr_init()\n", rc); Tec. Doc.07121901 Author-万一飞 /************************************************************/ /* 用 NULL 属性的方式创建一个新线程 */ printf("Create thread using the NULL attributes\n"); /* 接口数据赋值 */ parm = malloc(sizeof(thread_parm_t)); parm->value = 5; strcpy(parm->string, "Inside secondary thread"); /* 创建线程 */ rc = pthread_create(&thread, NULL, threadfunc, (void *)parm); checkResults("pthread_create(NULL)\n", rc); /************************************************************/ /* 使用默认的线程属性创建一个新线程 */ printf("Create thread using the default attributes\n"); /* 接口数据赋值 */ parm = malloc(sizeof(thread_parm_t)); parm->value = 77; strcpy(parm->string, "Inside secondary thread"); /* 创建线程 */ rc = pthread_create(&thread, &pta, threadfunc, (void *)parm); checkResults("pthread_create(&pta)\n", rc); /************************************************************/ printf("Destroy thread attributes object\n"); rc = pthread_attr_destroy(&pta); checkResults("pthread_attr_destroy()\n", rc); /* 通过 sleep() 的方式来等待线程结束,并不健壮 这里仅是举例表示等待线程结束而已 */ sleep(5); printf("Main completed\n"); return 0; } 7.7.2 pthread_exit 结束线程 语法: #include void pthread_exit(void *status); 功能: 这个函数用来结束一个运行中的线程,并返回该线程的状态。通常来说,这个状态是会 Professional Group 返回给创建这个子线程的线程。 Tec. Doc.07121901 Author-万一飞 当辅线程 return 的时候,系统会隐含地调用 pthread_exit 函数; 初始线程 return 的时候,系统则会隐含地调用 exit()函数 pthread_exit 函数与 exit()函数较为类似,不过只针对单条线程。 再次说明初始线程是较为特殊的,使用 pthread_exit()或是其它的方式结束初始线程,将 会导致整个进程的结束。 当使用 return,或 pthread_exit(结束),或 cancellation(取消)的方式结束一个线程的 时候,系统将会按照如下步骤进行处理: 1、 所以列入堆栈但未取出堆栈的,需要取消的程序,都会执行回滚操作,且该回滚不 可撤消。(Any cancellation cleanup handlers that have been pushed and not popped will be executed in reverse order with cancellation disabled.) 2、 Data destructors are called for any thread specific data entries that have a non NULL value for both the value and the destructor.(不会译) 3、 结束线程 4、 线程的结束有可能导致系统执行 cancel 类的处理(寄存器中#pragma cancel_handler 的指令) 5、 初始线程的结束,将会导致系统结束该进程下所有的其它线程,然后清除活动作业 组,调用 atexit()函数 6、 在被结束的线程中挂起的互斥体将处于“abandoned”状态,并且不再有效(Any mutexes that are held by a thread that terminates, become `abandoned' and are no longer valid)。之后如果其它的线程试图通过 pthread_mutex_lock 函数来获取这个互斥体 时,将会死锁;试图通过 pthread_mutex_trylock 函数来获取这个互斥体时,将么返 回 EBUSY 这个错误码。 7、 应用程序中可见的进程资源将不会被释放,这些资源包括但不仅限于:互斥体、文 件描述符,或其它进程级的 cleanup 操作。 参数: status 输入参数 表示该线程的状态 返回: 无返回 例子: #define _MULTI_THREADED #include #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } int theStatus=5; Professional Group Tec. Doc.07121901 void *threadfunc(void *parm) { printf("Inside secondary thread\n"); pthread_exit(__VOID(theStatus)); return __VOID(theStatus); /* Not needed, but this makes the compiler smile */ } Author-万一飞 int main(int argc, char **argv) { pthread_t thread; int rc=0; void *status; printf("Enter Testcase - %s\n", argv[0]); printf("Create thread using attributes that allow join\n"); rc = pthread_create(&thread, NULL, threadfunc, NULL); checkResults("pthread_create()\n", rc); printf("Wait for the thread to exit\n"); rc = pthread_join(thread, &status); checkResults("pthread_join()\n", rc); if (__INT(status) != theStatus) { printf("Secondary thread failed\n"); exit(1); } printf("Got secondary thread status as expected\n"); printf("Main completed\n"); return 0; } 7.7.3 pthread_join 等待线程结束并释放线程资源 语法: #include int pthread_join(pthread_t thread, void **status); 功能: 这个函数等待一个线程结束,分离这个线程(回收或不回收资源),然后返回线程的状 态。 如果 status 参数为 NULL,则不返回线程的状态。 线程的返回状态通常是由应用程序指定的,但下面这两种情况除外: 1、 线 程 使 用 pthread_cancel() 函 数 取 消 。 这 种 情 况 下 , 返 回 的 状 态 为 PTHREAD_CANCELLED Professional Group Tec. Doc.07121901 Author-万一飞 2、 线程被 exception 机制结束时,返回的状态为 PTHREAD_EXCEPTION_NP 最后,在线程属性中,detach 属性为 PTHREAD_CREATE_JOINABLE 这类线程使用 pthread_join, pthread_detach, pthread_extendedjoin_np 函 数 时 , 不 需 要 特 别 指 定 leaveThreadAllocated 这个选项,以便系统能回收分配给这些线程的资源。当对这类线程执 行 join to 或是 deatch 操作如果失败的话,将会导致内存泄露,直到进程结束为止。 参数: thread 输入参数 要操作线程的线程描述符,(在 pthread_create 中返回) status 输出参数 接收到的线程状态返回变量的地址。 返回: 0 函数成功执行 非 0 函数执行失败 错误信息: EINVAL 3021 函数参数不正确 ESRCH 3515 指定的线程不存在 例子: #define _MULTI_THREADED #include #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } int okStatus = 34; void *threadfunc(void *parm) { printf("Inside secondary thread\n"); return __VOID(okStatus); } int main(int argc, char **argv) { pthread_t thread; int rc=0; void *status; Professional Group printf("Enter Testcase - %s\n", argv[0]); printf("Create thread using attributes that allow join\n"); rc = pthread_create(&thread, NULL, threadfunc, NULL); checkResults("pthread_create()\n", rc); printf("Wait for the thread to exit\n"); rc = pthread_join(thread, &status); checkResults("pthread_join()\n", rc); if (__INT(status) != okStatus) { printf("Secondary thread failed\n"); exit(1); } printf("Got secondary thread status as expected\n"); printf("Main completed\n"); return 0; } Tec. Doc.07121901 Author-万一飞 7.7.4 pthread_join_np 等待线程结束 7.7.5 pthread_extendedjoin_np 根 据 一 些 扩 展 条 件等待线程 7.7.6 pthread_cancel 取消线程 语法: #include int pthread_cancel(pthread_t thread); 功能: 该函数用来取消一个指定的线程,所有基于该线程的应用都将被取消。 如果 cancelability 参数为 disable,那么所有的取消操作将被挂起,直到线程更改这个参 数。 如果 cancelability 参数为 deferred,那么所有的取消操作将被挂起,直到线程更改该参 数,同时调用 pthread_testcancel()产生一个取消操作的指针。 如果 cancelability 参数为 asynchronous,那么所有的取消操作将立刻执行,中断线程当 前的。 不能通过调用 pthread_setcanceltype 函数,更改 PTREAD_CANCEL_ASYNCHRONOUS 的方式来取消一个异步的线程。 Professional Group 取消线程时,系统操作与结束线程类似。 参数: thread 输入参数 要取消的线程的线程描述符 返回: 0 成功 非 0 失败 错误信息: EINVAL 3021 函数参数不正确 ESRCH 3515 指定的线程不存在 例子: #include #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } Tec. Doc.07121901 Author-万一飞 void *threadfunc(void *parm) { printf("Entered secondary thread\n"); while (1) { printf("Secondary thread is looping\n"); pthread_testcancel(); sleep(1); } return NULL; } int main(int argc, char **argv) { pthread_t thread; int rc=0; printf("Entering testcase\n"); /* Create a thread using default attributes */ Professional Group printf("Create thread using the NULL attributes\n"); rc = pthread_create(&thread, NULL, threadfunc, NULL); checkResults("pthread_create(NULL)\n", rc); Tec. Doc.07121901 Author-万一飞 /* sleep() is not a very robust way to wait for the thread */ sleep(2); printf("Cancel the thread\n"); rc = pthread_cancel(thread); checkResults("pthread_cancel()\n", rc); /* sleep() is not a very robust way to wait for the thread */ sleep(3); printf("Main completed\n"); return 0; } 8 线 程 特 有 数 据 API Thread specific storage API 线程的特有数据,就是一个针对当前线程的全局变量(一个 pthread_key_t 类型的变量, 其实也就是一个整型变量,其实也可以认为就是一个指针),这个指针将指向某些已分配空 间的地址。 我是这样理解: 在初始线程是使用 pthread_key_create 函数就类似于声明了这种特殊全局变量,即 指针,在调用了这个函数之后,初始线程每创建一个子线程,子线程就会为这个指针创 建一个当前子线程的线程内部共享的存储空间。同时 pthread_key_create 也会为这个指 针分配一个销毁函数。也就是说,对于每个子线程来说,这个指针自身所存在的地址和 它自身数值(即指向的地址)都是不同的。 然后在子线程内,需要首先通过 pthread_setspecific 函数将这个指针指向某片已分 配存储空间的地址。 于是子线程内的各个函数可以通过 pthread_getspecfic 函数取出这个指针所指向的 存储空间的数据。也就是通过这个特殊的,作用范围为单个线程的全局变量指针,实现 了线程内部的全局存储空间的共享。 最后,子线程结束时,系统自动调用销毁函数,free()和 pthread_setspecific(key, NULL) 是销毁函数中标准的处理方式,销毁函数中的其它语句可根据实际情况酌情撰写。 之所以要使用 pthread_setspecific 和 pthread_getspecific 函数来处理指针,而不是直 接处理存储空间,当然是基于线程安全性的考虑。这两个函数都具备线程安全性。 Professional Group 8.1 pthread_key_create Tec. Doc.07121901 Author-万一飞 语法: #include int pthread_key_create( pthread_key_t *key, void (*destructor)(void *)); 功能: 这个函数创建了一个针对当前线程的线程特有数据 key,同时为这个 key 分配了一个销 毁函数(destructor fuction)。这时,销毁函数的入口参数即与这个 key 对应起来。 key 创建之后,可以用来存放和读取(set & get)每个线程自已所拥有的数据的指针。 当线程结束时,如果 key 自身以及 key 所指向的值都不为空的话,那么系统将会调用销 毁函数。我们应该在销毁函数中,将 key 值,以及 key 所指向的值都清空。 如上所述,销毁函数的参数,是当前 key。 不要在销毁函数中使用 pthread_exit 函数。 参数: 略 返回: 0 – 成功; 非 0 – 失败 例子: 在下面这个例子中,仅仅只是演示创建和删除线程特有数据的语法,并没有实际使用这 个线程特有数据,所以销毁函数并没有被调用。 #define _MULTI_THREADED #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } pthread_key_t tlsKey = 0; void globalDestructor(void *value) { printf("In the data destructor\n"); free(value); pthread_setspecific(tlsKey, NULL); } Professional Group int main(int argc, char **argv) { int rc=0; int i=0; Tec. Doc.07121901 Author-万一飞 printf("Enter Testcase - %s\n", argv[0]); printf("Create a thread local storage key\n"); rc = pthread_key_create(&tlsKey, globalDestructor); checkResults("pthread_key_create()\n", rc); /* The key can now be used from all threads */ printf("- The key can now be used from all threads\n"); printf("- in the process to storage thread local\n"); printf("- (but global to all functions in that thread)\n"); printf("- storage\n"); printf("Delete a thread local storage key\n"); rc = pthread_key_delete(tlsKey); checkResults("pthread_key_delete()\n", rc); /* The key and any remaining values are now gone. */ printf("Main completed\n"); return 0; } 8.2 pthread_key_delete 语法: #include int pthread_key_delete(pthread_key_t key); 功能: 删除之前创建的线程的特有数据(也就是那个 key)。这个 delete 函数不会运行任何销 毁函数。 这个 delete 函数通常在初始线程中进行删除。 参数: 略 返回: 0 – 正常; 非 0 – 失败 例子: 同 pthread_key_create,略 Professional Group 8.3 pthread_setspecific Tec. Doc.07121901 Author-万一飞 语法: #include int pthread_setspecific(pthread_key_t key, const void *value); 功能: 将线程特有数据 key 值指向本地线程所分配的存储空间 value 中,以便线程内的各个函 数都可以共享这个 value 参数: key 输入参数 value 表明 KEY 所指向的存储空间 返回: 0 – 成功 ; 非 0 -- 失败 例子: #define _MULTI_THREADED #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } #define NUMTHREADS 3 pthread_key_t tlsKey = 0; void globalDestructor(void *value) { printf("In global destructor\n"); free(value); pthread_setspecific(tlsKey, NULL); } void showGlobal(void) { void pthread_id_np_t *global; tid; global = pthread_getspecific(tlsKey); Professional Group Tec. Doc.07121901 pthread_getunique_np((pthread_t *)global, &tid); printf("showGlobal: global data stored for thread 0x%.8x %.8x\n", tid); } Author-万一飞 void *threadfunc(void *parm) { int rc; int *myThreadDataStructure; pthread_t me = pthread_self(); printf("Inside secondary thread\n"); myThreadDataStructure = malloc(sizeof(pthread_t) + sizeof(int) * 10); memcpy(myThreadDataStructure, &me, sizeof(pthread_t)); pthread_setspecific(tlsKey, myThreadDataStructure); showGlobal(); return NULL; } int main(int argc, char **argv) { pthread_t thread[NUMTHREADS]; int rc=0; int i=0; printf("Enter Testcase - %s\n", argv[0]); printf("Create a thread local storage key\n"); rc = pthread_key_create(&tlsKey, globalDestructor); checkResults("pthread_key_create()\n", rc); /* The key can now be used from all threads */ printf("Create %d threads using joinable attributes\n", NUMTHREADS); for (i=0; i void *pthread_getspecific(pthread_key_t key); 功能: 根据 key,取出线程内部共享的数据。 需要注意,这个函数返回的是共享数据的地址。 例子见 pthread_setspecific, 其它略。 Professional Group 9 取消线程 API Tec. Doc.07121901 Author-万一飞 Thread cancellation API 9.1 pthread_cancel 9.2 pthread_cleanup_peek_np 9.3 pthread_cleanup_pop 9.4 pthread_cleanup_push 9.5 pthread_getcancelstate_np 9.6 pthread_setcancelstate 9.7 pthread_setcanceltype 9.8 pthread_test_exit_np 9.9 pthread_testcancel 10 条件变量的 API 所谓条件变量,其实就是一个信号。线程在执行 pthread_cond_wait 函数时将当前线程 挂起,等待别的线程发出信号;别的线程执行 pthread_cond_signal 或 pthread_cond_broadcast 函数时,向系统发出一个信号,然后挂起的线程收到这个信号,于是就被唤醒,继续操作。 基于这种原理,我们就可以在应用程序中,针对不同的情况或事件来发出这个信号,也就可 以实现根据某个事件,或某个数值来唤醒我们所之前挂起的线程。 条件变量需要与互斥体配合使用,详见 pthread_cond_wait。 每个条件变量最后都必须使用 pthread_cond_destory 函数来销毁它。 当一个条件创建之后,就不能再 COPY 和或 MOVE 到新地址中,否则将不能再有效使 用。 Professional Group 10.1 pthread_cond_init Tec. Doc.07121901 Author-万一飞 语法: #include int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); 或 pthread_cond_t cond = PTHREAD_COND_INITIALIZER 功能: 对条件目标进行初始化。如果 attr 参数设置为 NULL,那么将使用默认参数。 如果已有如下定义: pthread_cond_t cond2; pthread_cond_t cond3; pthread_condattr_t attr; pthread_condattr_init(&attr); 那么下面这三句语句是等效的,都是对条件变量进行初始化,使用默认参数 pthread_cond_t cond1 = PTHREAD_MUTEX_INITIALIZER; pthread_cond_init(&cond2, NULL); pthread_cond_init(&cond3, &attr); 但是使用 PTHREAD_COND_INITIALIZER 这种方式对条件变量进行初始化,将不会立 刻 生 效 , 而 是 在 其 后 首 次 使 用 pthread_cond_wait 或 pthread_cond_timewait 或 pthread_cond_signal,或 pthread_cond_broadcast 函数时才会进行条件变量初始化。如果没有 使用这些函数,就直接用 pthread_cond_destroy 函数,系统将会报一个 EINVAL 的错误信息。 参数: cond 条件变量的地址 attr 初始化条件变量的 attr 的地址 返回: 0 – 成功; 非 0 -- 失败 例子: 下面这个例子,仅仅只是使用不同的方法,对几个条件变量进行了初始化,然后再销毁, 并没有真正使用条件变量进行应用处理。 #define _MULTI_THREADED #include #include #include "check.h" pthread_cond_t pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER; cond2; Professional Group pthread_cond_t cond3; Tec. Doc.07121901 Author-万一飞 int main(int argc, char **argv) { int rc=0; pthread_condattr_t attr; printf("Enter Testcase - %s\n", argv[0]); printf("Create the default cond attributes object\n"); rc = pthread_condattr_init(&attr); checkResults("pthread_condattr_init()\n", rc); printf("Create the all of the default conditions in different ways\n"); rc = pthread_cond_init(&cond2, NULL); checkResults("pthread_cond_init()\n", rc); rc = pthread_cond_init(&cond3, &attr); checkResults("pthread_cond_init()\n", rc); printf("- At this point, the conditions with default attributes\n"); printf("- Can be used from any threads that want to use them\n"); printf("Cleanup\n"); pthread_condattr_destroy(&attr); pthread_cond_destroy(&cond1); pthread_cond_destroy(&cond2); pthread_cond_destroy(&cond3); printf("Main completed\n"); return 0; } 10.2 pthread_cond_wait 语法: #include int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 功能: 这个函数将会阻塞当前线程,等待条件产生(即某个线程发出信号)。 当执行这个函数时,必须先 lock 住指定的互斥体 mutex,在执行到 pthread_cond_wait 函数的语句时,将会对互斥体进行解锁操作,然后开始等待。 当等待的条件满足,或者线程被 cancel 了(线程被 cancel 时,虽然不会再执行应用程 Professional Group Tec. Doc.07121901 Author-万一飞 序的语句,但系统仍会就当前线程进行处理),在线程继续执行之前,当前线程将会首先自 动获取对互斥体的所有权(即锁住互斥体)。如果之前,当前线程未对互斥体执行 lock 操作, 那么将会返回一个 EPERM 的错误信息。如果此时别的线程锁住了互斥体,当前线程将处理 一个等待对方对互斥体解锁的状态中。在一个时点上,只能对一个条件变量分配一个互斥体。 在同一时间对一个条件变量分配两个互斥体将会导致应用程序出现无法预测的串行问题。 Pthread_cond_wait 这个函数可以被取消。 基于条件变量的原理,为了确保发出信号时,不会失效(即 pthread_cond_wait 操作在 pthread_cond_signal 之后发生),最好使用一个布尔型变量进行判断 参数: cond 输入参数,条件变量的地址 mutex 输入参数,分配给条件变量的互斥体的地址 返回: 0 – 成功; 非 0 –失败 例子: 在下面这个例子中,程序的执行流程大致上是这样的: 子线程 1 锁住互斥体,然后执行 pthread_cond_wait,解锁,等待; 子线程 2 锁住互斥体,然后执行 pthread_cond_wait,解锁,等待; 初始线程等两个子线程都开始执行 pthread_cond_wait(这里直接使用了 sleep 来实现等 待的目的。原程序段中也说明了,sleep 并不是一个很健壮的写法,这里只是举例) 然后锁住互斥体,更新关键数据 conditionMet(子线程根据这个值判断是否结果循环) 发出广播,唤醒所有等待的线程 然后初始线程对互斥体解锁(必须要解锁,不然子线程唤醒后重新获取互斥体的所有权, 即对互斥体进行锁操作,而此时互斥体又被初始线程锁住,然后初始线程又等待子线程结束, 于是形成死锁) 子线程 1 和子线程 2 同时被唤醒 子线程 1 获得互斥体的所有权,继续向下执行;子线程 2 在等待获取互斥体所有权。 子线程 1 判断循环的条件满足,退出循环,对互斥体解锁; 子线程 2 获取互斥体的所有权,也退出循环,对互斥体解锁。 最后初始线程等到了两个子线程的操作,回收资源,程序结束。 #define _MULTI_THREADED #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } /* For safe condition variable usage, must use a boolean predicate and */ /* a mutex with the condition. */ Professional Group int pthread_cond_t pthread_mutex_t Tec. Doc.07121901 conditionMet = 0; cond = PTHREAD_COND_INITIALIZER; mutex = PTHREAD_MUTEX_INITIALIZER; Author-万一飞 #define NTHREADS 5 void *threadfunc(void *parm) { int rc; rc = pthread_mutex_lock(&mutex); checkResults("pthread_mutex_lock()\n", rc); while (!conditionMet) { printf("Thread blocked\n"); rc = pthread_cond_wait(&cond, &mutex); checkResults("pthread_cond_wait()\n", rc); } rc = pthread_mutex_unlock(&mutex); checkResults("pthread_mutex_lock()\n", rc); return NULL; } int main(int argc, char **argv) { int rc=0; int i; pthread_t threadid[NTHREADS]; printf("Enter Testcase - %s\n", argv[0]); printf("Create %d threads\n", NTHREADS); for(i=0; i int pthread_cond_signal(pthread_cond_t *cond); 功能: 发出信号,用来唤起至少一个之前挂起的线程(具体作用范围不详,可以认为就只能唤 起一个线程?)。如果没有符合条件的线程,那么这个发出的信号将会作废,没有影响到任 何处理。 在执行 pthread_cond_wait 函数时,需要为条件变量分配一个互斥体。当前线程无论是 否拥有互斥体(即是否锁住),都可以执行 pthread_cond_signal 这个函数。当然,如果应用 程序需要的话,也可以在调用 pthread_cond_signal 这个函数之前锁住互斥体。 参数: cond 输入参数,条件变量的地址 返回: 0 – 成功; 非 0 – 失败 例子: #define _MULTI_THREADED #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ Professional Group } Tec. Doc.07121901 Author-万一飞 /* For safe condition variable usage, must use a boolean predicate and */ /* a mutex with the condition. */ int workToDo = 0; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; #define NTHREADS 2 void *threadfunc(void *parm) { int rc; while (1) { /* Usually worker threads will loop on these operations */ rc = pthread_mutex_lock(&mutex); checkResults("pthread_mutex_lock()\n", rc); while (!workToDo) { printf("Thread blocked\n"); rc = pthread_cond_wait(&cond, &mutex); checkResults("pthread_cond_wait()\n", rc); } printf("Thread awake, finish work!\n"); /* Under protection of the lock, complete or remove the work */ /* from whatever worker queue we have. Here it is simply a flag */ workToDo = 0; rc = pthread_mutex_unlock(&mutex); checkResults("pthread_mutex_lock()\n", rc); } return NULL; } int main(int argc, char **argv) { int rc=0; int i; pthread_t threadid[NTHREADS]; printf("Enter Testcase - %s\n", argv[0]); printf("Create %d threads\n", NTHREADS); Professional Group Tec. Doc.07121901 for(i=0; i #include int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, 功能: const struct timespec *abstime); 与 phtread_cond_wait 类似,但增加了超时设置,即超过指定时间后,该函数会返回一 个 ETIMEOUT 的错误信息。 Professional Group Tec. Doc.07121901 参数: 其它略。 Abstime 是一个独立的系统时间,注意设置方式。 返回: 0 – 成功; ETIMEOUT – 超进退出; 其它 – 失败 例子: 这个例子中,设置了超时等待的时间为 15 秒,注意对时间的处理。 #define _MULTI_THREADED #include #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } Author-万一飞 /* For safe condition variable usage, must use a boolean predicate and */ /* a mutex with the condition. */ int workToDo = 0; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; #define NTHREADS 3 #define WAIT_TIME_SECONDS 15 void *threadfunc(void *parm) { int rc; struct timespec ts; struct timeval tp; rc = pthread_mutex_lock(&mutex); checkResults("pthread_mutex_lock()\n", rc); /* Usually worker threads will loop on these operations */ while (1) { rc = gettimeofday(&tp, NULL); checkResults("gettimeofday()\n", rc); /* Convert from timeval to timespec */ ts.tv_sec = tp.tv_sec; ts.tv_nsec = tp.tv_usec * 1000; Professional Group ts.tv_sec += WAIT_TIME_SECONDS; Tec. Doc.07121901 Author-万一飞 while (!workToDo) { printf("Thread blocked\n"); rc = pthread_cond_timedwait(&cond, &mutex, &ts); /* If the wait timed out, in this example, the work is complete, and */ /* the thread will end. */ /* In reality, a timeout must be accompanied by some sort of checking */ /* to see if the work is REALLY all complete. In the simple example */ /* we will just go belly up when we time out. */ if (rc == ETIMEDOUT) { printf("Wait timed out!\n"); rc = pthread_mutex_unlock(&mutex); checkResults("pthread_mutex_lock()\n", rc); pthread_exit(NULL); } checkResults("pthread_cond_timedwait()\n", rc); } printf("Thread consumes work here\n"); workToDo = 0; } rc = pthread_mutex_unlock(&mutex); checkResults("pthread_mutex_lock()\n", rc); return NULL; } int main(int argc, char **argv) { int rc=0; int i; pthread_t threadid[NTHREADS]; printf("Enter Testcase - %s\n", argv[0]); printf("Create %d threads\n", NTHREADS); for(i=0; i int pthread_lock_global_np(void); 功能: 这个函数将会锁住一个在线程运行时,由系统提供的全局互斥体。这是个递归的互 斥体,名字为“QP0W_GLOBAL_MTX”。 递归时,最大的锁次数为 32767,超过这个数量时,将会返回 ERECURSE 的错误 码。 参数: 无 返回: 0 – 成功 非 0 -- 失败 例子: 注意下面例子的使用,对于这个系统创建的互斥体,我们不需要对其进行定义。同 时互斥体的调用是可以递归的,注意到在子线程中,先对互斥体进行了 lock 操作,然 后子线程调用的函数中再次对互斥体进行了 lock 操作。 这种可以递归的互斥体,在同一线程中的 lock 与 unlock 操作一定要匹配。 同时,递归仅限于当前线程之内。 在这个例子中,如果调用时不带参数,那么各个子线程实际将会执行串行操作,最 后得到正确的结果。 Give any number of parameters to show data corruption Creating 10 threads Wait for results Professional Group Using 10 threads and LOOPCONSTANT = 5000 Values are: (should be 50000) ==>50000, 50000, 50000, 50000 Main completed Tec. Doc.07121901 Author-万一飞 如果调用时带了参数(随便什么都可以),那么各个子线程将会实现并发操作,最 后得到一个预期以外的结果(结果应该是随机的,但一定小于正确的结果): Give any number of parameters to show data corruption A parameter was specified, no serialization is being done! Creating 10 threads Wait for results Using 10 threads and LOOPCONSTANT = 5000 Values are: (should be 50000) ==>34785, 37629, 48219, 47632 Main completed #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } /* This example shows the corruption that can result if no serialization is done and also shows the use of pthread_lock_global_np(). Call this test with no parameters to use pthread_lock_gloabl_np() to protect the critical data, between more than one (possibly unrelated) functions. Use 1 or more parameters to skip locking and show data corruption that occurs without locking. */ #define LOOPCONSTANT 5000 #define THREADS 10 int i,j,k,l; int uselock=1; void secondFunction(void) { int rc; if (uselock) { rc = pthread_lock_global_np(); checkResults("pthread_lock_global_np()\n", rc); Professional Group Tec. Doc.07121901 } --i; --j; --k; --l; if (uselock) { rc = pthread_unlock_global_np(); checkResults("pthread_unlock_global_np()\n", rc); } } Author-万一飞 void *threadfunc(void *parm) { int loop = 0; int rc; for (loop=0; loop%d, %d, %d, %d\n", i, j, k, l); printf("Main completed\n"); return 0; } 13.1.2 pthread_unlock_global_np 与 pthread_lock_globak_np 的使用方法类似,只不过是用于对全局互斥体的解锁操 作,略。 13.1.3 pthread_mutex_init 互斥体初始化 语法: #include int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); 或 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; Professional Group 功能: Tec. Doc.07121901 Author-万一飞 对互斥体进行初始化。如果 attr 参数调为 NULL,互斥体属性将会设置成为默认参数。 当已具备如下定义后: pthread_mutex_t mutex2; pthread_mutex_t mutex3; pthread_mutexattr_t mta; pthread_mutexattr_init(&mta); 下面这三种对互斥体进行初始化的方法是等价的,它们都将互斥体初始化为默认属性。 pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_init(&mutex2, NULL); pthread_mutex_init(&mutex3, &mta); 但是使用 PTHREAD_MUTEX_INITIALIZER 这种方式对互斥体进行初始化,将不会立 刻生效,而是在其后首次使用 pthread_mutex_lock 或 pthread_mutex_trylock 函数时才会进行 互斥体初始化。这是因为一个互斥体并不仅仅只是一个存储目标,它还需要系统分配相应的 资源。于是,用这种方法对互斥体进行初始化,如果在互斥体没有被锁住时就执行 pthread_mutex_destroy,或 pthread_mutex_unlock 操作,系统将会报一个 EINVAL 的错误信息。 参数: mutex 一个互斥体目标的地址。 Attr 标识互斥体属性目标的地址。(即互斥体属性结构体的地址位),可以为 NULL 返回: 0 -非0 例子: 略 成功 -- 失败 13.1.4 pthread_mutex_lock 互斥体锁 语法: #include int pthread_mutex_lock(pthread_mutex_t *mutex) 功能: 当一个线程对互斥体成功执行了 pthread_mutex_lock 操作后,其它的线程再对这个互斥 体执行 lock 操作时,将会被阻塞,直到当前线程对互斥体进行 unlock 操作。然后就会有另 一个等待执行 lock 操作的线程再对互斥体进行 lock 操作。 参数: mutex 要锁住的互斥体的地址。 返回: 0 成功; 非 0 失败 Professional Group Tec. Doc.07121901 Author-万一飞 例子: 下面这个例子中,通过使用互斥体,实现各个子线程实际上的串行。 当没有调用参数时,程序会使用互斥体,来实际各个子线程实际上的串行,达到对关键 数据保护的目的,最后将会计算得出预期中正确的结果。 如果有调用参数(随便什么参数均可),则程序将不会使用互斥体,子线程之间并行, 对关键数据没有保护,此时将会计算出一个非预期的随机的错误结果。 #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } #define #define LOOPCONSTANT 10000 THREADS 10 pthread_mutex_t int int mutex = PTHREAD_MUTEX_INITIALIZER; i,j,k,l; uselock=1; void *threadfunc(void *parm) { int loop = 0; int rc; for (loop=0; loop%d, %d, %d, %d\n", i, j, k, l); printf("Main completed\n"); return 0; } 13.1.5 pthread_mutex_unlock 互斥体解锁 与 pthread_mutex_lock 类似,只不过是解锁指令,略 Professional Group Tec. Doc.07121901 Author-万一飞 13.1.6 pthread_mutex_destroy 销毁互斥体 语法: #include int pthread_mutex_destroy(pthread_mutex_t *mutex) 功能: 销毁指定的互斥体,销毁后的互斥体将不能再使用。 互斥体只能由它的拥有者来销毁,如果别的线程锁住互斥体,当前线程执行 pthread_mutex_destroy 函数时,将会得到一个 EBUSY 的错误信息。 如果销毁互斥体时,其它的通过调用 pthread_mutex_lock 函数,正处于阻塞状态中的线 程将会由系统返回信息,并得到一个 EDESTROYED 的错误信息。 参数: mutex 互斥体结构的地址。 返回: 0 – 正常; 非 0 -- 失败 例子: 见 pthread_mutex_lock 中的例子,略。 13.1.7 pthread_mutex_timedlock_np (带超时设置的 互斥体锁) 语法: #include #include int pthread_mutex_timedlock_np(pthread_mutex_t *mutex, const struct timespec *deltatime); 功能: 带超时设置的互斥体的 lock 操作。即如果当前互斥体已被别的线程锁住,那么当前线 程会阻塞等待,超过指定的时间之后,将会返回一个 EBUSY 的返回信息码。 参数: mutex 指定的互斥体 deltatime 指定的超时时长 返回: 0 -- 成功; EBUSY(3029) – 超时退出; 其它值 – 其它错误; 例子: 这个例子中,主线程一直锁住了互斥体,如果直接使用 pthread_mutex_lock 操作的话, 子线程将会与直等待,而主线程又等待子线程的结束,结果将会造成死锁。 所以说,合理使用 pthread_mutex_timedlock_np,以及 pthread_mutex_trylock,可以有效 的避免死锁。 Professional Group #define _MULTI_THREADED #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } Tec. Doc.07121901 Author-万一飞 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void *threadFunc(void *parm) { int rc; int i; struct timespec deltatime; deltatime.tv_sec = 5; deltatime.tv_nsec = 0; printf("Timed lock the mutex from a secondary thread\n"); rc = pthread_mutex_timedlock_np(&mutex, &deltatime); if (rc != EBUSY) { printf("Got an incorrect return code from pthread_mutex_timedlock_np\n"); } printf("Thread mutex timeout\n"); return 0; } int main(int argc, char **argv) { int rc=0; pthread_t thread; printf("Enter Testcase - %s\n", argv[0]); printf("Acquire the mutex in the initial thread\n"); rc = pthread_mutex_lock(&mutex); checkResults("pthread_mutex_lock()\n", rc); printf("Create a thread\n"); rc = pthread_create(&thread, NULL, threadFunc, NULL); checkResults("pthread_create()\n", rc); Professional Group printf("Join to the thread\n"); rc = pthread_join(thread, NULL); checkResults("pthread_join()\n", rc); printf("Destroy mutex\n"); pthread_mutex_destroy(&mutex); printf("Main completed\n"); return 0; } Tec. Doc.07121901 Author-万一飞 13.1.8 pthread_mutex_trylock (不进行阻塞处理的 互斥体锁) 语法: #include int pthread_mutex_trylock (pthread_mutex_t *mutex) 功能: 这个函数试图锁住一个互斥体,但并不进行阻塞处理。也就是如果执行时,发现该互斥 体已被别的线程锁住时,函数将不会阻塞,而是返回一个 EBUSY 的错误信息。 如果当前线程已锁住这个互斥体时,函数将会返回一个 EDEADLK 的错误信息。 参数、返回与函数 pthread_mutex_lock 相同,略。 例子: 这个例子比较具有实用,因为通过 pthread_mutex_lock 来控制保护关键数据,造成了对 关键数据的操作实质上是处于串行中。 此处通过非阻塞的 pthread_mutex_trylock,充分利用了各个线程自已的资源去进行计算, 同时尽可能的实时更改关键数据。最后,计算出各个线程本地计算量的百分比。得出的结果 显示大部分计算量都是由各个线程自已完成,程序效率大大提高。 最后输出的参考结果: Creating 10 threads Wait for results Thread processed about 95% of the problem locally Thread processed about 93% of the problem locally Thread processed about 88% of the problem locally Thread processed about 85% of the problem locally Thread processed about 98% of the problem locally Thread processed about 95% of the problem locally Thread processed about 94% of the problem locally Thread processed about 91% of the problem locally Thread processed about 81% of the problem locally Thread processed about 60% of the problem locally Professional Group Cleanup and show results Using 10 threads and LOOPCONSTANT = 100000 Values are: (should be 1000000) ==>1000000, 1000000, 1000000, 1000000 Main completed Tec. Doc.07121901 Author-万一飞 #include #include #include #define checkResults(string, val) { \ if (val) { \ printf("Failed with %d at %s", val, string); \ exit(1); \ } \ } /* This example simulates a number of threads working on a parallel problem. The threads use pthread_mutex_trylock() so that they do not spend time blocking on a mutex and instead spend more of the time making progress towards the final solution. When trylock fails, the processing is done locally, eventually to be merged with the final parallel solution. This example should complete faster than the example for pthread_mutex_lock() in which threads solve the same parallel problem but spend more time waiting in resource contention. */ #define LOOPCONSTANT 10000 #define THREADS 10 pthread_mutex_t int mutex = PTHREAD_MUTEX_INITIALIZER; i,j,k,l; void *threadfunc(void *parm) { int loop = 0; int localProcessingCompleted = 0; int numberOfLocalProcessingBursts = 0; int processingCompletedThisBurst = 0; int rc; for (loop=0; loop%d, %d, %d, %d\n", i, j, k, l); printf("Main completed\n"); return 0; } 13.2 互斥体属性设置 13.2.1 pthread_mutexattr_init 互斥体属性初始化 13.2.2 pthread_mutexattr_destroy 销毁互斥体属性 Professional Group Tec. Doc.07121901 Author-万一飞 13.2.3 pthread_mutexattr_setkind_np 设置互斥体 种类属性 13.2.4 pthread_mutexattr_setname_np 设置互斥体 属性名字 13.2.5 pthread_mutexattr_setpshared 设 置 互 斥 体 中进程属性 13.2.6 pthread_mutexattr_settype 设置互斥体类型 属性 13.2.7 pthread_mutexattr_default_np 设 置 互 斥 体 为默认属性 13.3 取互斥体属性 API – Mutex Attribute API 13.3.1 pthread_mutexattr_getkind_np 取 互 斥 体 种 类 13.3.2 pthread_mutexattr_getname_np 取 互 斥 体 属 性目标名 13.3.3 pthread_mutexattr_getpshared 从互斥体中取 出进程共享的属性 13.3.4 pthread_mutexattr_gettype 取互斥体类型 Professional Group 14 信号量的 API Tec. Doc.07121901 Author-万一飞 与信号量相关的 API,其实操作的都是一个信号量组(semaphore set),或者是一套信号 量,总之就是多个信号量的集合。一个信号量组里面,会有多个信号量。 14.1 semget – 带 KEY 值取信号量描述符 语法: #include #include int semget(key_t key, int nsems, int semflg); 功能: semget 这个函数既可以创建一个新的信号量组并返回其描述符,也可以通过 KEY 值返 回一个已存在的信号量组的描述符。当满足以下任一一个条件时,该函数将会创建新的信号 量组: 5、 key 参数为 IPC_PRIVATE 6、 根据 key 参数没有找到已在的信号量组, 同时 semflg 参数为 IPC_CREAT(此 时生成的信号量组即带 key 参数) 该函数不会更改已存在的信号量组的状态。 当创建新信号量组时,系统将会对 semid_ds 结构体中的成员进行如下的初始化赋值: sem_perm.cuid, sem_perm.uid 将会赋值为当前线程的用户 ID; sem_perm.guid, sem_perm.gid 将会赋值为当前线程的 group ID; sem_perm.mode 的低 9 位,赋值成为输入参数 semflg 的低 9 位。 sem_nsems 赋值成为输入参数 nsems sem_ctime 赋值成为当前时间 sem_otime 赋值为 zero 参数: KEY 输入参数,根据此 KEY 查找,或生成信号量组。 这个参数为 IPC_PRIVATE(0x00000000)时,此时会生成一个信号量组。 用户可以自行指定这个参数,或由函数 ftok()来产生 nsems 输入参数,应该是仅在生成信号量时使用。表明该信号量组中,信号量的数量。 当信号量生成之后,这个值不能再更改。如果访问的是一个已存在的信号量,这个 值可以赋为 zero.. Semflg 输入参数,操作权限的标识。可以赋值为 zero,或是以下的值: S_IRUSR 允许该信号量的拥有者从中读取数据 Professional Group Tec. Doc.07121901 Author-万一飞 S_IWUSR 允许该信号量的拥有者向其中写数据 S_IRGRP 允许该信号量的用户组从中读取数据 S_IWGRP 允许该信号量的用户组向其中写数据 S_IROTH 允许其它用户读取该组信号量 S_IWOTH 允许其它用户向该信号量中写 IPC_CREAT 如果相应 KEY 值的信号量不存在,那么建立新的信号量 IPC_EXCL 如果设置了 IPC_CREAT,而信号量已存在时,返回错误 返回: -1 其它值 函数发生错误 该信号量的唯一标识符 例子: 如下面这个不完整的例子中,定义了一个整型变量 semaphoreID,做为信号量的描述符, 要赋初始值为-1,即与函数的错误返回相同,以便错误判断处理。 这里就创建了一个信号量组,该组中只有一个信号量。 #include #include int semaphoreID = -1 ; semaphoreId = semget(IPC_PRIVATE, 1, S_IRUSR|S_IWUSR); 14.2 semop 对信号量组进行操作 语法: #include int semop(int semid, struct sembuf *sops, size_t nsops); 功能: 对已存在的信号量组执行操作。 根据输入的参数,该命令可以对一组信号量中的多个信号量执行操作,也可以对同一信 号量执行多次操作。(可能后者的用法会比较少吧,虽然理论上是可行的) 参数: semid 信号量组的描述符 sops 一个数组的指针,该数组是由一个 sembuf 结构的结构体组成。这个结构体用标明 Professional Group Tec. Doc.07121901 Author-万一飞 用来具体的操作内容项。 sembuf 结构体的定义如下: struct sembuf { /* sops 即是 semaphore operation structure 的简称*/ unsigned short sem_num; /* 信号量组中,要操作的信号量的编号(位置) */ short sem_op; /* 操作数值 */ short sem_flg; /* 操作标志 SEM_UNDO and IPC_NOWAIT */ } 该数组中,一条记录(即一个结构体),就标识了一次操作。如果有多条记录(多 个结构体),那么可以执行多次操作。 结构体中的 sem_num 变量标识的是信号量组中,指定的信号量的编号,由 0 开始。 即该组信号量中,第一个信号量的编号为 0,第二个信号量为 1。 Nsops 上述数组中的记录个数(即结构体的个数),也就是要执行的操作次数。 返回: 0 操作成功 非 0 操作失败 注意事项: 系统根据入口参数中的 sops,逐笔对信号量执行操作直至完毕。当执行 sem_op 操作时, 其它的线程都不能再操作这个信号量组,直到当前线程结束操作,或被挂起。 如果结构体中的 sem_op 参数是正数,函数将会增加指定的信号量的数值,然后唤起相 应数量的?重新运行条件为信号量增加而的线程,这也就相当于通过信号量释放资源。 如果结构体中的 sem_op 参数是负数,函数将会减去指定的信号量的数值。 当结果为负数的时候,线程将会挂起,直到该信号量增加;(即当前线程重新运行 条件为指定的信号量增加) 如果结果是正数,就仅仅只是减去信号量的数值而已; 如果结果是 0,将会唤起相应数量的?重新运行条件为信号量数值等于 0 的线程。 如果结构体中的 sem_op 的参数为 0, 就表示挂起当前线程,直到指定的信号量数值为 0. 如果 sem_flg 参数设置为 IPC_NOWAIT,那么当前线程不能运行时,将不会被挂起,而 是直接返回 EAGAIN 的错误码。 如果 sem_flg 参数设置为 SEM_UNDO,那么当线程结束时,对指定信号量的操作将会 回滚,也就是通过信号量来控制资源的回收与申请。 如果 sem_flg 参数设置为 0,就表示只进行标准处理,没有其它特殊操作。 当使用 sem_op 操作挂起线程的时候,这个线程可以被其它的线程中断。 例子: 在下面这个例子里,先生成了一个信号量组,这个组里只有一个信号量。然后对这个信 号量进行操作。 LockOperation 就是一个 sembuf 结构的变量, 第一位 0.,表示操作信号量组中的第一个信号量; Professional Group 第二位 -1,表示对信号量进行减操作; 第三位 0, 表示没有特殊处理 Tec. Doc.07121901 Author-万一飞 在执行 semop 函数时, 首位参数 semaporeId,表示信号量组的描述符; 第二位参数传递的是 lockOperation 地址 第三位参数 1,表示第二位参数传递的内容中,只含一次操作的内容。 #include #include int rc; int semaphoreID = -1 ; struct sembuf lockOperation = { 0, -1, 0}; semaphoreId = semget(IPC_PRIVATE, 1, S_IRUSR|S_IWUSR); rc = semop(semaphoreId, &lockOperation, 1); 14.3 semctl -- 对信号量进行控制操作 语法: #include int semctl(int semid, int semnum, int cmd, 可选参数); 功能: 这个函数允许调用者控制指定的信号量。 调用者通过 cmd 参数,来表达需要进行控制的内容: IPC_RMID(0x00000000) 从系统中移除信号量组描述符,销毁信号量组。所有执行 sem_op 操作后,基于这 个信号量组而挂起的线程,将会返回一个-1, 同时错误码为 EIDRM IPC_SET (0x00000001) 通过第 4 个可选参数,设置 semid_ds 数据结构中的 sem_perm.uid,sem_perm.gid, sem_perm.mode 的值。这里,第 4 个参数此时是一个指向 semid_ds 类型结构体的指针。 IPC_STAT (0x00000002) 通过第 4 个可选参数,保存 semid_ds 数据结构中的当前值。第 4 个参数此时是一 个指向 semid_ds 类型结构体的指针。 GETNCNT (0x00000003) 返回唤起条件为“指定的信号量数值增加”的线程数量。这个数值对应 semaphore_t 结构中的 semncnt。 GETPID (0x00000004) 返回最后一个操作指定信号量的线程所属的进程 ID。这个数值对应 semaphore_t 结构中的 sempid. Professional Group Tec. Doc.07121901 Author-万一飞 GETVAL(0x00000005) 返回指定的信号量的当前数值。这个数值对应 semaphore_t 结构中的 semval. GETALL(0x00000006) 通过第四个可选参数,返回这个信号量组中,所有信号量当前的值。这个参数此时 是一个数组指针,数组中元素类型为 unsigned short. GETZCNT(0x00000007) 返回唤起条件为“指定的信号量数值等于 0”的线程数量。这个数值对应 smeaphore_t 结构中的 semzcnt. SETVAL(0x00000008) 将指定的信号量的数值赋值为第 4 个可选参数,此时该参数类型需要为 int.同时清 除与信号量有关联的每个线程的信号量调整值(Set the value of semaphore semnum to the integer value of type int specified in the fourth parameter and clear the associated per-thread semaphore adjustment value.) SETALL(0x00000009) 通过第 4 个可选参数,将信号量组中的每一个信号量的值都进行相应更改。此时第 4 个参数是一个数组指针,数组中元素类型为 unsigned short.同时,与每个信号量有关 联 的 线 程 , 都 会 清 除 其 信 号 量 调 整 值 。( In addition, the associated per-thread semaphore-adjustment value is cleared for each semaphore) 参数: semid 输入参数, 正整数,信号量组描述符。 Semnum 输入参数,非负整数。标识指定信号量在信号量组中的编号。编号从 0 开始,即该 组信号量中,第一个信号量的编号为 0,第二个信号量的编号为 1. Cmd 控制符,见上文中的描述。 可选参数 根据具体情况而定,有时表示输入,有时表示输出。有时是一个整型变量,有时是 一个数组指针,详情见上文中的描述。 返回: 当返回成功时,视 cmd 参数的不同,会有不同的返回值 GETVAL 返回信号量的数值 GETPID 返回进程号 GETNCNT 返回符合条件的线程数量 GETZCNT 返回符合条件的线程数量 其它操作 返回 0 Professional Group Tec. Doc.07121901 Author-万一飞 当函数失败时,返回-1 例子: 下面这个简单的例子中,接上面 semop 操作的内容,对信号量组进行了 SETVAL 的控 制操作,将这个信号量组中的第一个信号量的数值设置为 1. #include #include int rc; int semaphoreID = -1 ; struct sembuf lockOperation = { 0, -1, 0}; semaphoreId = semget(IPC_PRIVATE, 1, S_IRUSR|S_IWUSR); rc = semop(semaphoreId, &lockOperation, 1); rc = semctl(semaphoreId, 0, SETVAL, (int)1); 15 其它 15.1 spawn 语法: #include pid_t spawn( const char const int const int const struct inheritance char * const 功能: char * const *path, fd_count, fd_map[], *inherit, argv[], envp[]); 参数: path 输入参数,可执行文件名。 Fd_count 输入参数,子进程可以继承的文件描述符 fd_map[] 输入参数,子进程接收到当前进程传递给它的文件描述符数组 inherit 输入参数,一个 inheritance 类型的结构的地址。 返回: Professional Group Tec. Doc.07121901 Author-万一飞 例子: 下面这个例子演示了通过 spawn 函数,创建一个进程,新的进程继承了当前进程的 socket 描述符。 /**************************************************************************/ /* Application creates an child process using spawn(). */ /**************************************************************************/ #include #include #include #include #include #define SERVER_PORT 12345 main (int argc, char *argv[]) { int i, num, pid, rc, on = 1; int listen_sd, accept_sd; int spawn_fdmap[1]; char *spawn_argv[1]; char *spawn_envp[1]; struct inheritance inherit; struct sockaddr_in addr; /*************************************************/ /* If an argument was specified, use it to */ /* control the number of incoming connections */ /*************************************************/ if (argc >= 2) num = atoi(argv[1]); else num = 1; /*************************************************/ /* Create an AF_INET stream socket to receive */ /* incoming connections on */ /*************************************************/ listen_sd = socket(AF_INET, SOCK_STREAM, 0); if (listen_sd < 0) { perror("socket() failed"); exit(-1); } Professional Group Tec. Doc.07121901 Author-万一飞 /*************************************************/ /* Allow socket descriptor to be reuseable */ /*************************************************/ rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); if (rc < 0) { perror("setsockopt() failed"); close(listen_sd); exit(-1); } /*************************************************/ /* Bind the socket */ /*************************************************/ memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(SERVER_PORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); rc = bind(listen_sd, (struct sockaddr *)&addr, sizeof(addr)); if (rc < 0) { perror("bind() failed"); close(listen_sd); exit(-1); } /*************************************************/ /* Set the listen back log */ /*************************************************/ rc = listen(listen_sd, 5); if (rc < 0) { perror("listen() failed"); close(listen_sd); exit(-1); } /*************************************************/ /* Inform the user that the server is ready */ /*************************************************/ printf("The server is ready\n"); Professional Group Tec. Doc.07121901 Author-万一飞 /*************************************************/ /* Go through the loop once for each connection */ /*************************************************/ for (i=0; i < num; i++) { /**********************************************/ /* Wait for an incoming connection */ /**********************************************/ printf("Interation: %d\n", i+1); printf(" waiting on accept()\n"); accept_sd = accept(listen_sd, NULL, NULL); if (accept_sd < 0) { perror("accept() failed"); close(listen_sd); exit(-1); } printf(" accept completed successfully\n"); /**********************************************/ /* Initialize the spawn parameters */ /* */ /* The socket descriptor for the new */ /* connection is mapped over to descriptor 0 */ /* in the child program. */ /**********************************************/ memset(&inherit, 0, sizeof(inherit)); spawn_argv[0] = NULL; spawn_envp[0] = NULL; spawn_fdmap[0] = accept_sd; /**********************************************/ /* Create the worker job */ /**********************************************/ printf(" creating worker job\n"); pid = spawn("/QSYS.LIB/QGPL.LIB/WRKR1.PGM", 1, spawn_fdmap, &inherit, spawn_argv, spawn_envp); if (pid < 0) { perror("spawn() failed"); close(listen_sd); close(accept_sd); Professional Group exit(-1); } printf(" spawn completed successfully\n"); Tec. Doc.07121901 Author-万一飞 /**********************************************/ /* Close down the incoming connection since */ /* it has been given to a worker to handle */ /**********************************************/ close(accept_sd); } /*************************************************/ /* Close down the listen socket */ /*************************************************/ close(listen_sd); } 语法: 功能: 参数: 返回: 例子:

Top_arrow
回到顶部
EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高进,我们会尽快处理。