《多线程编程指南.pdf》由会员分享,可在线阅读,更多相关《多线程编程指南.pdf(328页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、多线程编程指南SunMicrosystems,Inc.4150NetworkCircleSantaClara,CA95054U.S.A.文件号码8197051102006年10月版权所有2005SunMicrosystems,Inc.4150NetworkCircle,SantaClara,CA95054U.S.A.保留所有权利。本文档及其相关产品的使用、复制、分发和反编译均受许可证限制。未经Sun及其许可方(如果有)的事先书面许可,不得以任何形式、任何手段复制本产品或文档的任何部分。第三方软件,包括字体技术,均已从Sun供应商处获得版权和使用许可。本产品的某些部分可能是从BerkeleyBS
2、D系统衍生出来的,并获得了加利福尼亚大学的许可。UNIX是X/OpenCompany,Ltd.在美国和其他国家/地区独家许可的注册商标。Sun、SunMicrosystems、Sun徽标、AnswerBook、AnswerBook2、和Solaris是SunMicrosystems,Inc.在美国和其他国家/地区的商标或注册商标。所有SPARC商标的使用均已获得许可,它们是SPARCInternational,Inc.在美国和其他国家/地区的商标或注册商标。标有SPARC商标的产品均基于由SunMicrosystems,Inc.开发的体系结构。OPENLOOK和SunTM图形用户界面是SunM
3、icrosystems,Inc.为其用户和许可证持有者开发的。Sun感谢Xerox在研究和开发可视或图形用户界面的概念方面为计算机行业所做的开拓性贡献。Sun已从Xerox获得了对Xerox图形用户界面的非独占性许可证,该许可证还适用于实现OPENLOOKGUI和在其他方面遵守Sun书面许可协议的Sun许可证持有者。美国政府权利商业软件。政府用户应遵循SunMicrosystems,Inc.的标准许可协议,以及FAR(FederalAcquisitionRegulations,即“联邦政府采购法规”)的适用条款及其补充条款。本文档按“原样”提供,对于所有明示或默示的条件、陈述和担保,包括对适销
4、性、适用性或非侵权性的默示保证,均不承担任何责任,除非此免责声明的适用范围在法律上无效。06122915821目录前言.111多线程基础介绍.15定义多线程术语.15符合多线程标准.16多线程的益处.17提高应用程序的响应.17有效使用多处理器.17改进程序结构.17占用较少的系统资源.17结合线程和RPC(远程过程调用).18多线程概念.18并发性和并行性.18多线程结构一览.18线程调度.19线程取消.19线程同步.20使用64位体系结构.202基本线程编程.23线程库.23创建缺省线程.23等待线程终止.25简单线程的示例.26分离线程.28为线程特定数据创建键.29删除线程特定数据键.
5、303设置线程特定数据.31获取线程特定数据.32获取线程标识符.36比较线程ID.36初始化线程.37停止执行线程.38设置线程的优先级.38获取线程的优先级.39向线程发送信号.40访问调用线程的信号掩码.41安全地Fork.42终止线程.42结束.43取消线程.43取消线程.45启用或禁用取消功能.45设置取消类型.46创建取消点.47将处理程序推送到栈上.48从栈中弹出处理程序.483线程属性.51属性对象.51初始化属性.52销毁属性.53设置分离状态.54获取分离状态.55设置栈溢出保护区大小.56获取栈溢出保护区大小.57设置范围.58获取范围.59设置线程并行级别.60获取线程
6、并行级别.60设置调度策略.61获取调度策略.62目录多线程编程指南 2006年10月4设置继承的调度策略.63获取继承的调度策略.64设置调度参数.64获取调度参数.65设置栈大小.67获取栈大小.68关于栈.69设置栈地址和大小.71获取栈地址和大小.734用同步对象编程.75互斥锁属性.76初始化互斥锁属性对象.77销毁互斥锁属性对象.78设置互斥锁的范围.79获取互斥锁的范围.80设置互斥锁类型的属性.80获取互斥锁的类型属性.82设置互斥锁属性的协议.82获取互斥锁属性的协议.84设置互斥锁属性的优先级上限.85获取互斥锁属性的优先级上限.86设置互斥锁的优先级上限.87获取互斥锁的
7、优先级上限.88设置互斥锁的强健属性.89获取互斥锁的强健属性.90使用互斥锁.91初始化互斥锁.92使互斥保持一致.93锁定互斥锁.94解除锁定互斥锁.96使用非阻塞互斥锁锁定.97销毁互斥锁.98互斥锁定的代码示例.99条件变量属性.105目录5初始化条件变量属性.106删除条件变量属性.106设置条件变量的范围.107获取条件变量的范围.108使用条件变量.109初始化条件变量.109基于条件变量阻塞.111解除阻塞一个线程.112在指定的时间之前阻塞.114在指定的时间间隔内阻塞.116解除阻塞所有线程.117销毁条件变量状态.119唤醒丢失问题.120生成方和使用者问题.120使用信
8、号进行同步.124命名信号和未命名信号.125计数信号量概述.125初始化信号.126增加信号.128基于信号计数进行阻塞.129减小信号计数.129销毁信号状态.130使用信号时的生成方和使用者问题.131读写锁属性.133初始化读写锁属性.134销毁读写锁属性.134设置读写锁属性.135获取读写锁属性.136使用读写锁.136初始化读写锁.137获取读写锁中的读锁.138读取非阻塞读写锁中的锁.139写入读写锁中的锁.139写入非阻塞读写锁中的锁.140解除锁定读写锁.140销毁读写锁.141目录多线程编程指南 2006年10月6跨进程边界同步.142生成方和使用者问题示例.142比较元
9、语.1455使用Solaris软件编程.147进程创建中的fork问题.147Fork-One模型.148Fork-all模型.151选择正确的Fork.151进程创建:exec和exit问题.152计时器、报警与剖析.152每LWPPOSIX计时器.152每线程报警.153剖析多线程程序.153非本地转向:setjmp和longjmp.154资源限制.154LWP和调度类.154分时调度.155实时调度.155公平共享调度程序.155固定优先级调度.156扩展传统信号.156同步信号.157异步信号.157延续语义.157对信号执行的操作.158定向于线程的信号.160完成语义.162信号处
10、理程序和异步信号安全.163中断对条件变量的等待.165I/O问题.166I/O作为远程过程调用.167人为的异步性.167异步I/O.167共享的I/O和新的I/O系统调用.169目录7getc和putc的替代项.1696安全和不安全的接口.171线程安全.171MT接口安全级别.173不安全接口的可重复执行函数.174异步信号安全函数.175库的MT安全级别.175不安全库.1767编译和调试.177编译多线程应用程序.177为编译做准备.177选择Solaris语义或POSIX语义.178包括或.178定义_REENTRANT或_POSIX_C_SOURCE.179使用libthread
11、或libpthread链接.179与POSIX信号的-lrt链接.181将原有模块与新模块链接.181备用线程库.181调试多线程程序.181多线程程序中常见的疏忽性问题.181使用TNF实用程序跟踪和调试.182使用truss.182使用mdb.182使用dbx.1838Solaris线程编程.185比较Solaris线程和POSIX线程的API.185API的主要差异.185函数比较表.186Solaris线程的独有函数.189暂停执行线程.190继续执行暂停的线程.191目录多线程编程指南 2006年10月8相似的同步函数读写锁.192初始化读写锁.192获取读锁.194尝试获取读锁.1
12、94获取写锁.195尝试获取写锁.196解除锁定读写锁.196销毁读写锁的状态.197相似的Solaris线程函数.199创建线程.199获取最小栈大小.201获取线程标识符.202停止执行线程.202向线程发送信号.203访问调用线程的信号掩码.203终止线程.204等待线程终止.204创建线程特定的数据键.206设置线程特定的数据值.207获取线程特定的数据值.208设置线程的优先级.208获取线程的优先级.210相似的同步函数互斥锁.210初始化互斥锁.210销毁互斥锁.213获取互斥锁.213释放互斥锁.214尝试获取互斥锁.214相似的同步函数:条件变量.215初始化条件变量.215
13、销毁条件变量.216等待条件.217等待绝对时间.218等待时间间隔.218解除阻塞一个线程.219解除阻塞所有线程.220目录9相似的同步函数:信号.220初始化信号.220增加信号.222基于信号计数阻塞.222减小信号计数.223销毁信号状态.224跨进程边界同步.224生成方和使用者问题示例.224fork()和Solaris线程的特殊问题.2279编程原则.229重新考虑全局变量.229提供静态局部变量.230同步线程.231单线程策略.232可重复执行函数.232避免死锁.234与调用相关的死锁.235锁定原则.235线程代码的一些基本原则.236创建和使用线程.236使用多处理器
14、.237基础体系结构.237线程程序示例.241需要进一步阅读的内容.241A样例应用程序:多线程grep.243tgrep的说明.243BSolaris线程示例:barrier.c.301索引.311目录多线程编程指南 2006年10月10前言多线程编程指南介绍了SolarisTM操作系统(SolarisOperatingSystem,SolarisOS)中POSIX线程和Solaris线程的多线程编程接口。本指南将指导应用程序程序员如何创建新的多线程程序以及如何向现有的程序中添加多线程。尽管本指南同时介绍了POSIX线程接口和Solaris线程接口,但大多数主题都以POSIX线程为重点。仅
15、适用于Solaris线程的信息将专门在一章中介绍。要理解本指南,读者必须熟悉并发编程的概念:UNIXSVR4系统首选是Solaris发行版。C编程语言多线程接口由标准C库提供。并发编程(与顺序编程相对)的原理。注本Solaris发行版支持使用SPARC和x86系列处理器体系结构的系统:UltraSPARC、SPARC64、AMD64、Pentium和XeonEM64T。支持的系统可以在http:/ have mail.前言多线程编程指南 2006年10月12表P1印刷约定(续)字体或符号含义示例AaBbCc123用户键入的内容,与计算机屏幕输出的显示不同machine_name%suPassw
16、ord:AaBbCc123要使用实名或值替换的命令行占位符要删除文件,请键入rmfilename。AaBbCc123保留未译的新词或术语以及要强调的词这些称为class选项。新词术语强调新词或术语以及要强调的词必须成为超级用户才能执行此操作。书名书名阅读用户指南的第6章。命令中的shell提示符示例下表列出了Cshell、Bourneshell和Kornshell的缺省系统提示符和超级用户提示符。表P2Shell提示符Shell提示符Cshellmachine_name%Cshell超级用户machine_name#Bourneshell和Kornshell$Bourneshell和Korns
17、hell超级用户#前言1314多线程基础介绍多线程一词可以解释为多个控制线程或多个控制流。虽然传统的UNIX进程包含单个控制线程,但多线程(multithreading,MT)会将一个进程分成许多执行线程,其中每个线程都可独立运行。本章介绍了一些多线程的术语和概念及其所产生的益处。如果您已准备好开始使用多线程,请跳至第2章。第15页中的“定义多线程术语”第16页中的“符合多线程标准”第17页中的“多线程的益处”第18页中的“多线程概念”定义多线程术语表11介绍了本书中所使用的一些术语。表11多线程术语术语定义Process(进程)通过fork(2)系统调用创建的UNIX环境(如文件描述符和用户
18、ID等),为运行程序而设置。Thread(线程)在进程上下文中执行的指令序列。POSIXpthread符合POSIX线程的线程接口。Solaristhread(Solaris线程)不符合POSIX线程的SunMicrosystemsTM线程接口,pthread的前序节点。single-threaded(单线程)仅允许访问一个线程。Multithreading(多线程)允许访问两个或多个线程。1第1章15表11多线程术语(续)术语定义User-levelorApplication-levelthread(用户级线程或应用程序级线程)在用户空间(而非内核空间)中由线程调度例程管理的线程。Light
19、weightprocess(轻量进程)用来执行内核代码和系统调用的内核线程,又称作LWP。从Solaris9开始,每个线程都有一个专用的LWP。Boundthread(绑定线程)(过时的术语)指的是在Solaris9之前,和一个LWP永久绑定的用户级线程。从Solaris9开始,每个线程都有一个专用的LWP。Unboundthread(非绑定线程)(过时的术语)指的是在Solaris9之前,无须和一个LWP绑定的用户级线程。从Solaris9开始,每个线程都有一个专用的LWP。Attributeobject(属性对象)包含不透明数据类型和相关处理函数。这些数据类型和函数可以对POSIX线程一些
20、可配置的方面,例如互斥锁(mutex)和条件变量,进行标准化。Mutualexclusionlock(互斥锁)用来锁定和解除锁定对共享数据访问的函数。Conditionvariable(条件变量)用来阻塞线程直到状态发生变化的函数。Read-writelock(读写锁)可用于对共享数据进行多次只读访问的函数,但是要修改共享数据则必须以独占方式访问。Countingsemaphore(计数信号量)一种基于内存的同步机制。Parallelism(并行性)如果至少有两个线程正在同时执行,则会出现此情况。Concurrency(并发性)如果至少有两个线程正在进行,则会出现此情况。并发是一种更广义的并行
21、性,其中可以包括分时这种形式的虚拟并行性。符合多线程标准多线程编程的概念至少可以回溯到二十世纪六十年代。多线程编程在UNIX系统中的发展是从八十年代中期开始的。虽然对多线程的定义以及对支持多线程所需要的功能存在共识,但是用于实现多线程的接口有很大不同。在过去的几年内,POSIX(PortableOperatingSystemInterface,可移植操作系统接口)1003.4a工作小组一直致力于制定多线程编程标准。现在,该标准已得到认可。该多线程编程指南基于POSIX标准IEEEStd1003.11996版(又称作ISO/IEC99451第二版)。最新修订版的POSIX标准IEEEStd100
22、3.1:2001(又称作ISO/IEC9945:2002和单一UNIX规范版本3)中也提供了这些功能。特定于Solaris线程的主题将在第8章中进行介绍。符合多线程标准多线程编程指南 2006年10月16多线程的益处本节简要介绍多线程的益处。在代码中实现多线程具有以下益处:提高应用程序的响应更有效地使用多处理器改进程序结构占用较少的系统资源提高应用程序的响应可以对任何一个包含许多相互独立的活动的程序进行重新设计,以便将每个活动定义为一个线程。例如,多线程GUI的用户不必等待一个活动完成即可启动另一个活动。有效使用多处理器通常,要求并发线程的应用程序无需考虑可用处理器的数量。使用额外的处理器可以
23、明显提高应用程序的性能。具有高度并行性的数值算法和数值应用程序(如矩阵乘法)在多处理器上通过多个线程实现时,运行速度会快得多。改进程序结构许多应用程序都以更有效的方式构造为多个独立或半独立的执行单元,而非整块的单个线程。多线程程序比单线程程序更能适应用户需求的变化。占用较少的系统资源如果两个或多个进程通过共享内存访问公用数据,则使用这些进程的程序可以实现对多个线程的控制。但是,每个进程都有一个完整的地址空间和操作环境状态。每个进程用于创建和维护大量状态信息的成本,与一个线程相比,无论是在时间上还是空间上代价都更高。此外,进程间所固有的独立性使得程序员需要花费很多精力来处理不同进程间线程的通信或
24、者同步这些线程的操作。多线程的益处第1章 多线程基础介绍17结合线程和RPC(远程过程调用)通过将多个线程和一个远程过程调用(remoteprocedurecall,RPC)结合起来,可以充分利用无共享内存的多处理器(如工作站集合)。这种结合将工作站集合视为一个多处理器,从而使应用程序的分布变得相对容易些。例如,一个线程可以创建多个子线程,每个子线程随后可以请求远程过程调用,从而调用另一个工作站上的过程。尽管初始线程此时仅创建了一些并行运行的线程,但是这种并行性会涉及到其他计算机。多线程概念本节介绍多线程的基本概念。并发性和并行性在单个处理器的多线程进程中,处理器可以在线程之间切换执行资源,从
25、而执行并发。在共享内存的多处理器环境内的同一个多线程进程中,进程中的每个线程都可以在一个单独的处理器上并发运行,从而执行并行。如果进程中的线程数不超过处理器的数目,则线程的支持系统和操作环境可确保每个线程在不同的处理器上执行。例如,在线程数和处理器数目相同的矩阵乘法中,每个线程和每个处理器都会计算一行结果。多线程结构一览传统的UNIX已支持多线程的概念。每个进程都包含一个线程,因此对多个进程进行编程即是对多个线程进行编程。但是,进程同时也是一个地址空间,因此创建进程会涉及到创建新的地址空间。创建线程比创建新进程成本低,因为新创建的线程使用的是当前进程的地址空间。相对于在进程之间切换,在线程之间
26、进行切换所需的时间更少,因为后者不包括地址空间之间的切换。在进程内部的线程间通信很简单,因为这些线程会共享所有内容,特别是地址空间。所以,一个线程生成的数据可以立即用于其他所有线程。在Solaris9和较早的Solaris发行版中,支持多线程的接口是通过特定的子例程库实现的。这些子例程库包括用于POSIX线程的libpthread和用于Solaris线程的libthread。多线程通过将内核级资源和用户级资源分离来提供灵活性。在当前的发行版中,对于这两组接口的多线程支持是由标准C库提供的。多线程概念多线程编程指南 2006年10月18用户级线程线程是多线程编程中的主编程接口。线程仅在进程内部是
27、可见的,进程内部的线程会共享诸如地址空间、打开的文件等所有进程资源。用户级线程状态以下状态对于每个线程是唯一的。线程ID寄存器状态(包括PC和栈指针)栈信号掩码优先级线程专用存储由于线程可共享进程指令和大多数进程数据,因此一个线程对共享数据进行的更改对进程内其他线程是可见的。一个线程需要与同一个进程内的其他线程交互时,该线程可以在不涉及操作系统的情况下进行此操作。注顾名思义,用户级线程不同于内核级线程,只有系统程序员才能处理内核级线程。由于本书面向应用程序程序员,因此将不讨论内核级线程。线程调度POSIX标准指定了三种调度策略:先入先出策略(SCHED_FIFO)、循环策略(SCHED_RR)
28、和自定义策略(SCHED_OTHER)。SCHED_FIFO是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR与FIFO相似,不同的是前者的每个线程都有一个执行时间配额。SCHED_FIFO和SCHED_RR是对POSIXRealtime的扩展。SCHED_OTHER是缺省的调度策略。有关SCHED_OTHER策略的信息,请参见第154页中的“LWP和调度类”。提供了两个调度范围:进程范围(PTHREAD_SCOPE_PROCESS)和系统范围(PTHREAD_SCOPE_SYSTEM)。具有不同范围状态的线程可以在同一个系统甚至同一个进程中共存。进程范围只允许这种线程与
29、同一进程中的其他线程争用资源,而系统范围则允许此类线程与系统内的其他所有线程争用资源。实际上,从Solaris9发行版开始,系统就不再区分这两个范围。线程取消一个线程可以请求终止同一个进程中的其他任何线程。目标线程(要取消的线程)可以延后取消请求,并在该线程处理取消请求时执行特定于应用程序的清理操作。多线程概念第1章 多线程基础介绍19通过pthread取消功能,可以对线程进行异步终止或延迟终止。异步取消可以随时发生,而延迟取消只能发生在所定义的点。延迟取消是缺省类型。线程同步使用同步功能,可以控制程序流并访问共享数据,从而并发执行多个线程。共有四种同步模型:互斥锁、读写锁、条件变量和信号。互
30、斥锁仅允许每次使用一个线程来执行特定的部分代码或者访问特定数据。读写锁允许对受保护的共享资源进行并发读取和独占写入。要修改资源,线程必须首先获取互斥写锁。只有释放所有的读锁之后,才允许使用互斥写锁。条件变量会一直阻塞线程,直到特定的条件为真。计数信号量通常用来协调对资源的访问。使用计数,可以限制访问某个信号的线程数量。达到指定的计数时,信号将阻塞。使用64位体系结构对于应用程序开发者,Solaris64位和32位环境的主要区别在于所使用的C语言数据类型的模型。64位数据类型使用LP64模型,其中long和指针的宽度为64位,其他所有基础数据类型仍然与32位实现的数据类型相同。32位数据类型使用
31、ILP32模型,其中的int、long和指针宽度为32位。以下简要概述了64位环境的主要特征以及使用该环境时的注意事项:大虚拟地址空间在64位环境中,进程的虚拟地址空间最高可达64位(即18EB)。目前,32位进程的最大地址空间为4GB,较大的虚拟地址空间大约是其40亿倍。但是由于硬件限制,某些平台可能并不支持完整的64位地址空间。大地址空间增加了可创建的具有缺省栈大小的线程数。在32位和64位系统中,栈的大小分别为1MB和2MB。在32位和64位系统中,具有缺省栈大小的线程数分别是大约2000个和80000亿个。内核内存读取器内核是在内部使用64位数据结构的LP64对象。这意味着,使用lib
32、kvm、/dev/mem或/dev/kmem的现有32位应用程序不能正常工作,必须转换为64位程序。/proc限制使用/proc的32位程序可以查看32位进程,但是无法识别64位进程。用来描述进程的现有接口和数据结构不够大,因此无法包含64位值。对于此类程序,必须将其重新编译为64位程序,使其可同时适用于32位进程和64位进程。64位库使用64位体系结构多线程编程指南 2006年10月2032位库必须与32位应用程序进行链接,而64位库必须与64位应用程序进行链接。除已过时的库以外,所有的系统库都同时提供32位版本和64位版本。64位运算64位运算早已在以前的32位Solaris发行版中提供。
33、现在,64位实现提供了完整的64位计算机寄存器,用于进行整数运算和参数传递。大文件如果应用程序仅要求大文件支持,则可以保留32位并使用大文件接口。要充分利用64位功能,必须将应用程序转换为64位。使用64位体系结构第1章 多线程基础介绍2122基本线程编程本章介绍POSIX线程的基本线程编程例程。本章介绍缺省线程(即,具有缺省属性值的线程),这是多线程编程中最常用的线程。本章还介绍如何创建和使用具有非缺省属性的线程。本章介绍的POSIX例程具有与最初的Solaris多线程库相似的编程接口。线程库下面简要论述了特定任务及其相关手册页。创建缺省线程如果未指定属性对象,则该对象为NULL,系统会创建
34、具有以下属性的缺省线程:进程范围非分离缺省栈和缺省栈大小零优先级还可以用pthread_attr_init()创建缺省属性对象,然后使用该属性对象来创建缺省线程。有关详细信息,请参见第52页中的“初始化属性”一节。pthread_create语法使用pthread_create(3C)可以向当前进程中添加新的受控线程。intpthread_create(pthread_t*tid,const pthread_attr_t*tattr,void*(*start_routine)(void*),void*arg);2第2章23#include pthread_attr_t()tattr;pthre
35、ad_t tid;extern void*start_routine(void*arg);void*arg;int ret;/*default behavior*/ret=pthread_create(&tid,NULL,start_routine,arg);/*initialized with default attributes*/ret=pthread_attr_init(&tattr);/*default behavior specified*/ret=pthread_create(&tid,&tattr,start_routine,arg);使用具有必要状态行为的attr调用pthr
36、ead_create()函数。start_routine是新线程最先执行的函数。当start_routine返回时,该线程将退出,其退出状态设置为由start_routine返回的值。请参见第23页中的“pthread_create语法”。当pthread_create()成功时,所创建线程的ID被存储在由tid指向的位置中。使用NULL属性参数或缺省属性调用pthread_create()时,pthread_create()会创建一个缺省线程。在对tattr进行初始化之后,该线程将获得缺省行为。pthread_create返回值pthread_create()在调用成功完成之后返回零。其他任
37、何返回值都表示出现了错误。如果检测到以下任一情况,pthread_create()将失败并返回相应的值。EAGAIN描述:超出了系统限制,如创建的线程太多。线程库多线程编程指南 2006年10月24EINVAL描述:tattr的值无效。等待线程终止pthread_join()函数会一直阻塞调用线程,直到指定的线程终止。pthread_join语法使用pthread_join(3C)等待线程终止。intpthread_join(thread_t tid,void*status);#include pthread_t tid;int ret;void*status;/*waiting to joi
38、n thread tid with status*/ret=pthread_join(tid,&status);/*waiting to join thread tid without status*/ret=pthread_join(tid,NULL);指定的线程必须位于当前的进程中,而且不得是分离线程。有关线程分离的信息,请参见第54页中的“设置分离状态”。当status不是NULL时,status指向某个位置,在pthread_join()成功返回时,将该位置设置为已终止线程的退出状态。如果多个线程等待同一个线程终止,则所有等待线程将一直等到目标线程终止。然后,一个等待线程成功返回。其余
39、的等待线程将失败并返回ESRCH错误。在pthread_join()返回之后,应用程序可回收与已终止线程关联的任何数据存储空间。线程库第2章 基本线程编程25pthread_join返回值调用成功完成后,pthread_join()将返回零。其他任何返回值都表示出现了错误。如果检测到以下任一情况,pthread_join()将失败并返回相应的值。ESRCH描述:没有找到与给定的线程ID相对应的线程。EDEADLK描述:将出现死锁,如一个线程等待其本身,或者线程A和线程B互相等待。EINVAL描述:与给定的线程ID相对应的线程是分离线程。pthread_join()仅适用于非分离的目标线程。如果
40、没有必要等待特定线程终止之后才进行其他处理,则应当将该线程分离。简单线程的示例在示例21中,一个线程执行位于顶部的过程,该过程首先创建一个辅助线程来执行fetch()过程。fetch()执行复杂的数据库查找操作,查找过程需要花费一些时间。主线程将等待查找结果,但同时还执行其他操作。因此,主线程将执行其他活动,然后通过执行pthread_join()等待辅助线程。将新线程的pbe参数作为栈参数进行传递。这个线程参数之所以能够作为栈参数传递,是因为主线程会等待辅助线程终止。不过,首选方法是使用malloc从堆分配存储,而不是传递指向线程栈存储的地址。如果将该参数作为地址传递到线程栈存储,则该地址可
41、能无效或者在线程终止时会被重新分配。示例21简单线程程序void mainline(.)struct phonebookentry*pbe;pthread_attr_t tattr;pthread_t helper;void*status;线程库多线程编程指南 2006年10月26示例21简单线程程序(续)pthread_create(&helper,NULL,fetch,&pbe);/*do something else for a while*/pthread_join(helper,&status);/*its now safe to use result*/void*fetch(str
42、uct phonebookentry*arg)struct phonebookentry*npbe;/*fetch value from a database*/npbe=search(prog_name)if(npbe!=NULL)*arg=*npbe;pthread_exit(0);struct phonebookentry char name64;char phonenumber32;char flags16;线程库第2章 基本线程编程27示例21简单线程程序(续)分离线程pthread_detach(3C)是pthread_join(3C)的替代函数,可回收创建时detachstate
43、属性设置为PTHREAD_CREATE_JOINABLE的线程的存储空间。pthread_detach语法intpthread_detach(thread_t tid);#include pthread_t tid;int ret;/*detach thread tid*/ret=pthread_detach(tid);pthread_detach()函数用于指示应用程序在线程tid终止时回收其存储空间。如果tid尚未终止,pthread_detach()不会终止该线程。pthread_detach返回值pthread_detach()在调用成功完成之后返回零。其他任何返回值都表示出现了错误。
44、如果检测到以下任一情况,pthread_detach()将失败并返回相应的值。EINVAL描述:tid是分离线程。ESRCH描述:tid不是当前进程中有效的未分离的线程。线程库多线程编程指南 2006年10月28为线程特定数据创建键单线程C程序有两类基本数据:局部数据和全局数据。对于多线程C程序,添加了第三类数据:线程特定数据。线程特定数据与全局数据非常相似,区别在于前者为线程专有。线程特定数据基于每线程进行维护。TSD(特定于线程的数据)是定义和引用线程专用数据的唯一方法。每个线程特定数据项都与一个作用于进程内所有线程的键关联。通过使用key,线程可以访问基于每线程进行维护的指针(void*
45、)。pthread_key_create语法intpthread_key_create(pthread_key_t*key,void(*destructor)(void*);#include pthread_key_t key;int ret;/*key create without destructor*/ret=pthread_key_create(&key,NULL);/*key create with destructor*/ret=pthread_key_create(&key,destructor);可以使用pthread_key_create(3C)分配用于标识进程中线程特定数据
46、的键。键对进程中的所有线程来说是全局的。创建线程特定数据时,所有线程最初都具有与该键关联的NULL值。使用各个键之前,会针对其调用一次pthread_key_create()。不存在对键(为进程中所有的线程所共享)的隐含同步。创建键之后,每个线程都会将一个值绑定到该键。这些值特定于线程并且针对每个线程单独维护。如果创建该键时指定了destructor函数,则该线程终止时,系统会解除针对每线程的绑定。当pthread_key_create()成功返回时,会将已分配的键存储在key指向的位置中。调用方必须确保对该键的存储和访问进行正确的同步。线程库第2章 基本线程编程29使用可选的析构函数dest
47、ructor可以释放过时的存储。如果某个键具有非NULLdestructor函数,而线程具有一个与该键关联的非NULL值,则该线程退出时,系统将使用当前的相关值调用destructor函数。destructor函数的调用顺序不确定。pthread_key_create返回值pthread_key_create()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_key_create()将失败并返回相应的值。EAGAIN描述:key名称空间已经用完。ENOMEM描述:此进程中虚拟内存不足,无法创建新键。删除线程特定数据键使用pthread_key_del
48、ete(3C)可以销毁现有线程特定数据键。由于键已经无效,因此将释放与该键关联的所有内存。引用无效键将返回错误。Solaris线程中没有类似的函数。pthread_key_delete语法intpthread_key_delete(pthread_key_t key);#include pthread_key_t key;int ret;/*key previously created*/ret=pthread_key_delete(key);如果已删除键,则使用调用pthread_setspecific()或pthread_getspecific()引用该键时,生成的结果将是不确定的。程序员
49、在调用删除函数之前必须释放所有线程特定资源。删除函数不会调用任何析构函数。反复调用pthread_key_create()和pthread_key_delete()可能会产生问题。如果pthread_key_delete()将键标记为无效,而之后key的值不再被重用,那么反复调用它们就会出现问题。对于每个所需的键,应当只调用pthread_key_create()一次。线程库多线程编程指南 2006年10月30pthread_key_delete返回值pthread_key_delete()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,pthread_key_delete()将失败并返回相应的值。EINVAL描述:key的值无效。设置线程特定数据使用pthread_setspecific(3C)可以为指定线程特定数据键设置线程特定绑定。pthread_setspecific语法intpthread_setspecific(pthread_key_t key,const void*value);#include pthread_key_t key;void*value;int ret;/*key previously created*/ret=pthread_setspecific(key,value);pthread_setspecific返回值pthr
限制150内