多线程编程指南.pdf

多线程编程指南.pdf

ID:51491642

大小:1.78 MB

页数:328页

时间:2020-03-25

上传者:asd881529
多线程编程指南.pdf_第1页
多线程编程指南.pdf_第2页
多线程编程指南.pdf_第3页
多线程编程指南.pdf_第4页
多线程编程指南.pdf_第5页
资源描述:

《多线程编程指南.pdf》由会员上传分享,免费在线阅读,更多相关内容在行业资料-天天文库

多线程编程指南SunMicrosystems,Inc.4150NetworkCircleSantaClara,CA95054U.S.A.文件号码819–7051–102006年10月 版权所有2005SunMicrosystems,Inc.4150NetworkCircle,SantaClara,CA95054U.S.A.保留所有权利。本文档及其相关产品的使用、复制、分发和反编译均受许可证限制。未经Sun及其许可方(如果有)的事先书面许可,不得以任何形式、任何手段复制本产品或文档的任何部分。第三方软件,包括字体技术,均已从Sun供应商处获得版权和使用许可。本产品的某些部分可能是从BerkeleyBSD系统衍生出来的,并获得了加利福尼亚大学的许可。UNIX是X/OpenCompany,Ltd.在美国和其他国家/地区独家许可的注册商标。Sun、SunMicrosystems、Sun徽标、docs.sun.com、AnswerBook、AnswerBook2、和Solaris是SunMicrosystems,Inc.在美国和其他国家/地区的商标或注册商标。所有SPARC商标的使用均已获得许可,它们是SPARCInternational,Inc.在美国和其他国家/地区的商标或注册商标。标有SPARC商标的产品均基于由SunMicrosystems,Inc.开发的体系结构。OPENLOOK和SunTM图形用户界面是SunMicrosystems,Inc.为其用户和许可证持有者开发的。Sun感谢Xerox在研究和开发可视或图形用户界面的概念方面为计算机行业所做的开拓性贡献。Sun已从Xerox获得了对Xerox图形用户界面的非独占性许可证,该许可证还适用于实现OPENLOOKGUI和在其他方面遵守Sun书面许可协议的Sun许可证持有者。美国政府权利-商业软件。政府用户应遵循SunMicrosystems,Inc.的标准许可协议,以及FAR(FederalAcquisitionRegulations,即“联邦政府采购法规”)的适用条款及其补充条款。本文档按“原样”提供,对于所有明示或默示的条件、陈述和担保,包括对适销性、适用性或非侵权性的默示保证,均不承担任何责任,除非此免责声明的适用范围在法律上无效。061229@15821 目录前言........................................................................................................................................................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删除线程特定数据键.................................................................................................................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获取线程并行级别.....................................................................................................................60设置调度策略..............................................................................................................................61获取调度策略..............................................................................................................................624多线程编程指南•2006年10月 目录设置继承的调度策略.................................................................................................................63获取继承的调度策略.................................................................................................................64设置调度参数..............................................................................................................................64获取调度参数..............................................................................................................................65设置栈大小..................................................................................................................................67获取栈大小..................................................................................................................................68关于栈...........................................................................................................................................69设置栈地址和大小.....................................................................................................................71获取栈地址和大小.....................................................................................................................734用同步对象编程..................................................................................................................................75互斥锁属性..........................................................................................................................................76初始化互斥锁属性对象............................................................................................................77销毁互斥锁属性对象.................................................................................................................78设置互斥锁的范围.....................................................................................................................79获取互斥锁的范围.....................................................................................................................80设置互斥锁类型的属性............................................................................................................80获取互斥锁的类型属性............................................................................................................82设置互斥锁属性的协议............................................................................................................82获取互斥锁属性的协议............................................................................................................84设置互斥锁属性的优先级上限...............................................................................................85获取互斥锁属性的优先级上限...............................................................................................86设置互斥锁的优先级上限........................................................................................................87获取互斥锁的优先级上限........................................................................................................88设置互斥锁的强健属性............................................................................................................89获取互斥锁的强健属性............................................................................................................90使用互斥锁..........................................................................................................................................91初始化互斥锁..............................................................................................................................92使互斥保持一致.........................................................................................................................93锁定互斥锁..................................................................................................................................94解除锁定互斥锁.........................................................................................................................96使用非阻塞互斥锁锁定............................................................................................................97销毁互斥锁..................................................................................................................................98互斥锁定的代码示例.................................................................................................................99条件变量属性...................................................................................................................................1055 目录初始化条件变量属性...............................................................................................................106删除条件变量属性...................................................................................................................106设置条件变量的范围...............................................................................................................107获取条件变量的范围...............................................................................................................108使用条件变量...................................................................................................................................109初始化条件变量.......................................................................................................................109基于条件变量阻塞...................................................................................................................111解除阻塞一个线程...................................................................................................................112在指定的时间之前阻塞..........................................................................................................114在指定的时间间隔内阻塞......................................................................................................116解除阻塞所有线程...................................................................................................................117销毁条件变量状态...................................................................................................................119唤醒丢失问题............................................................................................................................120生成方和使用者问题...............................................................................................................120使用信号进行同步..........................................................................................................................124命名信号和未命名信号..........................................................................................................125计数信号量概述.......................................................................................................................125初始化信号................................................................................................................................126增加信号....................................................................................................................................128基于信号计数进行阻塞..........................................................................................................129减小信号计数............................................................................................................................129销毁信号状态............................................................................................................................130使用信号时的生成方和使用者问题....................................................................................131读写锁属性........................................................................................................................................133初始化读写锁属性...................................................................................................................134销毁读写锁属性.......................................................................................................................134设置读写锁属性.......................................................................................................................135获取读写锁属性.......................................................................................................................136使用读写锁........................................................................................................................................136初始化读写锁............................................................................................................................137获取读写锁中的读锁...............................................................................................................138读取非阻塞读写锁中的锁......................................................................................................139写入读写锁中的锁...................................................................................................................139写入非阻塞读写锁中的锁......................................................................................................140解除锁定读写锁.......................................................................................................................140销毁读写锁................................................................................................................................1416多线程编程指南•2006年10月 目录跨进程边界同步...............................................................................................................................142生成方和使用者问题示例......................................................................................................142比较元语............................................................................................................................................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信号处理程序和异步信号安全.............................................................................................163中断对条件变量的等待..........................................................................................................165I/O问题..............................................................................................................................................166I/O作为远程过程调用............................................................................................................167人为的异步性............................................................................................................................167异步I/O......................................................................................................................................167共享的I/O和新的I/O系统调用..........................................................................................1697 目录getc和putc的替代项.............................................................................................................1696安全和不安全的接口......................................................................................................................171线程安全............................................................................................................................................171MT接口安全级别............................................................................................................................173不安全接口的可重复执行函数.............................................................................................174异步信号安全函数..........................................................................................................................175库的MT安全级别...........................................................................................................................175不安全库....................................................................................................................................1767编译和调试........................................................................................................................................177编译多线程应用程序......................................................................................................................177为编译做准备............................................................................................................................177选择Solaris语义或POSIX语义............................................................................................178包括..........................................................................................178定义_REENTRANT或_POSIX_C_SOURCE..................................................................................179使用libthread或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继续执行暂停的线程...............................................................................................................1918多线程编程指南•2006年10月 目录相似的同步函数-读写锁.............................................................................................................192初始化读写锁............................................................................................................................192获取读锁....................................................................................................................................194尝试获取读锁............................................................................................................................194获取写锁....................................................................................................................................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销毁条件变量............................................................................................................................216等待条件....................................................................................................................................217等待绝对时间............................................................................................................................218等待时间间隔............................................................................................................................218解除阻塞一个线程...................................................................................................................219解除阻塞所有线程...................................................................................................................2209 目录相似的同步函数:信号..................................................................................................................220初始化信号................................................................................................................................220增加信号....................................................................................................................................222基于信号计数阻塞...................................................................................................................222减小信号计数............................................................................................................................223销毁信号状态............................................................................................................................224跨进程边界同步...............................................................................................................................224生成方和使用者问题示例......................................................................................................224fork()和Solaris线程的特殊问题................................................................................................2279编程原则............................................................................................................................................229重新考虑全局变量..........................................................................................................................229提供静态局部变量..........................................................................................................................230同步线程............................................................................................................................................231单线程策略................................................................................................................................232可重复执行函数.......................................................................................................................232避免死锁............................................................................................................................................234与调用相关的死锁...................................................................................................................235锁定原则....................................................................................................................................235线程代码的一些基本原则.............................................................................................................236创建和使用线程...............................................................................................................................236使用多处理器...................................................................................................................................237基础体系结构............................................................................................................................237线程程序示例...................................................................................................................................241需要进一步阅读的内容..........................................................................................................241A样例应用程序:多线程grep.........................................................................................................243tgrep的说明.....................................................................................................................................243BSolaris线程示例:barrier.c........................................................................................................301索引.....................................................................................................................................................31110多线程编程指南•2006年10月 前言TM《多线程编程指南》介绍了Solaris操作系统(SolarisOperatingSystem,SolarisOS)中POSIX®线程和Solaris线程的多线程编程接口。本指南将指导应用程序程序员如何创建新的多线程程序以及如何向现有的程序中添加多线程。尽管本指南同时介绍了POSIX线程接口和Solaris线程接口,但大多数主题都以POSIX线程为重点。仅适用于Solaris线程的信息将专门在一章中介绍。要理解本指南,读者必须熟悉并发编程的概念:®■UNIXSVR4系统-首选是Solaris发行版。■C编程语言-多线程接口由标准C库提供。■并发编程(与顺序编程相对)的原理。注–本Solaris发行版支持使用SPARC®和x86系列处理器体系结构的系统:UltraSPARC®、SPARC64、AMD64、Pentium和XeonEM64T。支持的系统可以在http://www.sun.com/bigadmin/hcl上的《Solaris10HardwareCompatibilityList》中找到。本文档列举了在不同类型的平台上进行实现时的所有差别。在本文档中,术语"x86"是指使用与AMD64或IntelXeon/Pentium产品系列兼容的处理器生产的64位和32位系统。有关受支持的系统的信息,请参见《Solaris10HardwareCompatibilityList》。本指南的结构第1章概述本发行版中线程实现的结构。第2章讨论常规POSIX线程例程,其中重点介绍如何创建具有缺省属性的线程。第3章介绍如何创建具有非缺省属性的线程。第4章介绍线程同步例程。第5章讨论为支持多线程而对操作环境进行的更改。第6章介绍多线程的安全问题。第7章介绍编译和调试多线程应用程序的基本信息。11 前言第8章介绍Solaris线程(与POSIX线程相对)接口。第9章讨论会影响程序员编写多线程应用程序的问题。附录A说明如何为POSIX线程设计代码。附录B举例说明如何在Solaris线程中构建屏障。联机访问Sun文档SM可以通过docs.sun.comWeb站点联机访问Sun技术文档。您可以浏览docs.sun.com文档库或查找某个特定的书名或主题。URL为http://docs.sun.com。相关书籍多线程技术要求以一种不同的方式来考虑函数交互。建议阅读以下书籍:■由AlanBurns和GeoffDavies合著的《ConcurrentProgramming》(Addison-Wesley出版,1993)。■由MichelRaynal编著的《DistributedAlgorithmsandProtocols》(Wiley出版,1998)。■由Silberschatz、Peterson和Galvin合著的《OperatingSystemConcepts》(Addison-Wesley出版,1991)。■由M.Ben-Ari编著的《PrinciplesofConcurrentProgramming》(Prentice-Hall出版,1982)。■由SteveKleiman、DevangShah和BartSmalders合著的《ProgrammingwithThreads》(PrenticeHall出版,1996)。印刷约定的含义下表介绍了本书中的印刷约定。表P–1印刷约定字体或符号含义示例AaBbCc123命令、文件和目录的名称;计算机屏幕输出编辑.login文件。使用ls-a列出所有文件。machine_name%youhavemail.12多线程编程指南•2006年10月 前言表P–1印刷约定(续)字体或符号含义示例AaBbCc123用户键入的内容,与计算机屏幕输出的显示machine_name%suPassword:不同AaBbCc123要使用实名或值替换的命令行占位符要删除文件,请键入rmfilename。AaBbCc123保留未译的新词或术语以及要强调的词这些称为class选项。新词术语强调新词或术语以及要强调的词必须成为超级用户才能执行此操作。《书名》书名阅读《用户指南》的第6章。命令中的shell提示符示例下表列出了Cshell、Bourneshell和Kornshell的缺省系统提示符和超级用户提示符。表P–2Shell提示符Shell提示符Cshellmachine_name%Cshell超级用户machine_name#Bourneshell和Kornshell$Bourneshell和Kornshell超级用户#13 14 第11章多线程基础介绍多线程一词可以解释为多个控制线程或多个控制流。虽然传统的UNIX进程包含单个控制线程,但多线程(multithreading,MT)会将一个进程分成许多执行线程,其中每个线程都可独立运行。本章介绍了一些多线程的术语和概念及其所产生的益处。如果您已准备好开始使用多线程,请跳至第2章。■第15页中的“定义多线程术语”■第16页中的“符合多线程标准”■第17页中的“多线程的益处”■第18页中的“多线程概念”定义多线程术语表1–1介绍了本书中所使用的一些术语。表1–1多线程术语术语定义Process(进程)通过fork(2)系统调用创建的UNIX环境(如文件描述符和用户ID等),为运行程序而设置。Thread(线程)在进程上下文中执行的指令序列。POSIXpthread符合POSIX线程的线程接口。Solaristhread(Solaris线程)不符合POSIX线程的SunMicrosystemsTM线程接口,pthread的前序节点。single-threaded(单线程)仅允许访问一个线程。Multithreading(多线程)允许访问两个或多个线程。15 符合多线程标准表1–1多线程术语(续)术语定义User-levelorApplication-level在用户空间(而非内核空间)中由线程调度例程管理的线程。thread(用户级线程或应用程序级线程)Lightweightprocess(轻量进程)用来执行内核代码和系统调用的内核线程,又称作LWP。从Solaris9开始,每个线程都有一个专用的LWP。Boundthread(绑定线程)(过指的是在Solaris9之前,和一个LWP永久绑定的用户级线程。从时的术语)Solaris9开始,每个线程都有一个专用的LWP。Unboundthread(非绑定线程)指的是在Solaris9之前,无须和一个LWP绑定的用户级线程。从(过时的术语)Solaris9开始,每个线程都有一个专用的LWP。Attributeobject(属性对象)包含不透明数据类型和相关处理函数。这些数据类型和函数可以对POSIX线程一些可配置的方面,例如互斥锁(mutex)和条件变量,进行标准化。Mutualexclusionlock(互斥锁)用来锁定和解除锁定对共享数据访问的函数。Conditionvariable(条件变量)用来阻塞线程直到状态发生变化的函数。Read-writelock(读写锁)可用于对共享数据进行多次只读访问的函数,但是要修改共享数据则必须以独占方式访问。Countingsemaphore(计数信号一种基于内存的同步机制。量)Parallelism(并行性)如果至少有两个线程正在同时执行,则会出现此情况。Concurrency(并发性)如果至少有两个线程正在进行,则会出现此情况。并发是一种更广义的并行性,其中可以包括分时这种形式的虚拟并行性。符合多线程标准多线程编程的概念至少可以回溯到二十世纪六十年代。多线程编程在UNIX系统中的发展是从八十年代中期开始的。虽然对多线程的定义以及对支持多线程所需要的功能存在共识,但是用于实现多线程的接口有很大不同。在过去的几年内,POSIX(PortableOperatingSystemInterface,可移植操作系统接口)1003.4a工作小组一直致力于制定多线程编程标准。现在,该标准已得到认可。该《多线程编程指南》基于POSIX标准IEEEStd1003.11996版(又称作ISO/IEC9945–1第二版)。最新修订版的POSIX标准IEEEStd1003.1:2001(又称作ISO/IEC9945:2002和单一UNIX规范版本3)中也提供了这些功能。特定于Solaris线程的主题将在第8章中进行介绍。16多线程编程指南•2006年10月 多线程的益处多线程的益处本节简要介绍多线程的益处。在代码中实现多线程具有以下益处:■提高应用程序的响应■更有效地使用多处理器■改进程序结构■占用较少的系统资源提高应用程序的响应可以对任何一个包含许多相互独立的活动的程序进行重新设计,以便将每个活动定义为一个线程。例如,多线程GUI的用户不必等待一个活动完成即可启动另一个活动。有效使用多处理器通常,要求并发线程的应用程序无需考虑可用处理器的数量。使用额外的处理器可以明显提高应用程序的性能。具有高度并行性的数值算法和数值应用程序(如矩阵乘法)在多处理器上通过多个线程实现时,运行速度会快得多。改进程序结构许多应用程序都以更有效的方式构造为多个独立或半独立的执行单元,而非整块的单个线程。多线程程序比单线程程序更能适应用户需求的变化。占用较少的系统资源如果两个或多个进程通过共享内存访问公用数据,则使用这些进程的程序可以实现对多个线程的控制。但是,每个进程都有一个完整的地址空间和操作环境状态。每个进程用于创建和维护大量状态信息的成本,与一个线程相比,无论是在时间上还是空间上代价都更高。此外,进程间所固有的独立性使得程序员需要花费很多精力来处理不同进程间线程的通信或者同步这些线程的操作。第1章•多线程基础介绍17 多线程概念结合线程和RPC(远程过程调用)通过将多个线程和一个远程过程调用(remoteprocedurecall,RPC)结合起来,可以充分利用无共享内存的多处理器(如工作站集合)。这种结合将工作站集合视为一个多处理器,从而使应用程序的分布变得相对容易些。例如,一个线程可以创建多个子线程,每个子线程随后可以请求远程过程调用,从而调用另一个工作站上的过程。尽管初始线程此时仅创建了一些并行运行的线程,但是这种并行性会涉及到其他计算机。多线程概念本节介绍多线程的基本概念。并发性和并行性在单个处理器的多线程进程中,处理器可以在线程之间切换执行资源,从而执行并发。在共享内存的多处理器环境内的同一个多线程进程中,进程中的每个线程都可以在一个单独的处理器上并发运行,从而执行并行。如果进程中的线程数不超过处理器的数目,则线程的支持系统和操作环境可确保每个线程在不同的处理器上执行。例如,在线程数和处理器数目相同的矩阵乘法中,每个线程和每个处理器都会计算一行结果。多线程结构一览传统的UNIX已支持多线程的概念。每个进程都包含一个线程,因此对多个进程进行编程即是对多个线程进行编程。但是,进程同时也是一个地址空间,因此创建进程会涉及到创建新的地址空间。创建线程比创建新进程成本低,因为新创建的线程使用的是当前进程的地址空间。相对于在进程之间切换,在线程之间进行切换所需的时间更少,因为后者不包括地址空间之间的切换。在进程内部的线程间通信很简单,因为这些线程会共享所有内容,特别是地址空间。所以,一个线程生成的数据可以立即用于其他所有线程。在Solaris9和较早的Solaris发行版中,支持多线程的接口是通过特定的子例程库实现的。这些子例程库包括用于POSIX线程的libpthread和用于Solaris线程的libthread。多线程通过将内核级资源和用户级资源分离来提供灵活性。在当前的发行版中,对于这两组接口的多线程支持是由标准C库提供的。18多线程编程指南•2006年10月 多线程概念用户级线程线程是多线程编程中的主编程接口。线程仅在进程内部是可见的,进程内部的线程会共享诸如地址空间、打开的文件等所有进程资源。用户级线程状态以下状态对于每个线程是唯一的。■线程ID■寄存器状态(包括PC和栈指针)■栈■信号掩码■优先级■线程专用存储由于线程可共享进程指令和大多数进程数据,因此一个线程对共享数据进行的更改对进程内其他线程是可见的。一个线程需要与同一个进程内的其他线程交互时,该线程可以在不涉及操作系统的情况下进行此操作。注–顾名思义,用户级线程不同于内核级线程,只有系统程序员才能处理内核级线程。由于本书面向应用程序程序员,因此将不讨论内核级线程。线程调度POSIX标准指定了三种调度策略:先入先出策略(SCHED_FIFO)、循环策略(SCHED_RR)和自定义策略(SCHED_OTHER)。SCHED_FIFO是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR与FIFO相似,不同的是前者的每个线程都有一个执行时间配额。SCHED_FIFO和SCHED_RR是对POSIXRealtime的扩展。SCHED_OTHER是缺省的调度策略。有关SCHED_OTHER策略的信息,请参见第154页中的“LWP和调度类”。提供了两个调度范围:进程范围(PTHREAD_SCOPE_PROCESS)和系统范围(PTHREAD_SCOPE_SYSTEM)。具有不同范围状态的线程可以在同一个系统甚至同一个进程中共存。进程范围只允许这种线程与同一进程中的其他线程争用资源,而系统范围则允许此类线程与系统内的其他所有线程争用资源。实际上,从Solaris9发行版开始,系统就不再区分这两个范围。线程取消一个线程可以请求终止同一个进程中的其他任何线程。目标线程(要取消的线程)可以延后取消请求,并在该线程处理取消请求时执行特定于应用程序的清理操作。第1章•多线程基础介绍19 使用64位体系结构通过pthread取消功能,可以对线程进行异步终止或延迟终止。异步取消可以随时发生,而延迟取消只能发生在所定义的点。延迟取消是缺省类型。线程同步使用同步功能,可以控制程序流并访问共享数据,从而并发执行多个线程。共有四种同步模型:互斥锁、读写锁、条件变量和信号。■互斥锁仅允许每次使用一个线程来执行特定的部分代码或者访问特定数据。■读写锁允许对受保护的共享资源进行并发读取和独占写入。要修改资源,线程必须首先获取互斥写锁。只有释放所有的读锁之后,才允许使用互斥写锁。■条件变量会一直阻塞线程,直到特定的条件为真。■计数信号量通常用来协调对资源的访问。使用计数,可以限制访问某个信号的线程数量。达到指定的计数时,信号将阻塞。使用64位体系结构对于应用程序开发者,Solaris64位和32位环境的主要区别在于所使用的C语言数据类型的模型。64位数据类型使用LP64模型,其中long和指针的宽度为64位,其他所有基础数据类型仍然与32位实现的数据类型相同。32位数据类型使用ILP32模型,其中的int、long和指针宽度为32位。以下简要概述了64位环境的主要特征以及使用该环境时的注意事项:■大虚拟地址空间在64位环境中,进程的虚拟地址空间最高可达64位(即18EB)。目前,32位进程的最大地址空间为4GB,较大的虚拟地址空间大约是其40亿倍。但是由于硬件限制,某些平台可能并不支持完整的64位地址空间。大地址空间增加了可创建的具有缺省栈大小的线程数。在32位和64位系统中,栈的大小分别为1MB和2MB。在32位和64位系统中,具有缺省栈大小的线程数分别是大约2000个和80000亿个。■内核内存读取器内核是在内部使用64位数据结构的LP64对象。这意味着,使用libkvm、/dev/mem或/dev/kmem的现有32位应用程序不能正常工作,必须转换为64位程序。■/proc限制使用/proc的32位程序可以查看32位进程,但是无法识别64位进程。用来描述进程的现有接口和数据结构不够大,因此无法包含64位值。对于此类程序,必须将其重新编译为64位程序,使其可同时适用于32位进程和64位进程。■64位库20多线程编程指南•2006年10月 使用64位体系结构32位库必须与32位应用程序进行链接,而64位库必须与64位应用程序进行链接。除已过时的库以外,所有的系统库都同时提供32位版本和64位版本。■64位运算64位运算早已在以前的32位Solaris发行版中提供。现在,64位实现提供了完整的64位计算机寄存器,用于进行整数运算和参数传递。■大文件如果应用程序仅要求大文件支持,则可以保留32位并使用大文件接口。要充分利用64位功能,必须将应用程序转换为64位。第1章•多线程基础介绍21 22 第22章基本线程编程本章介绍POSIX线程的基本线程编程例程。本章介绍缺省线程(即,具有缺省属性值的线程),这是多线程编程中最常用的线程。本章还介绍如何创建和使用具有非缺省属性的线程。本章介绍的POSIX例程具有与最初的Solaris多线程库相似的编程接口。线程库下面简要论述了特定任务及其相关手册页。创建缺省线程如果未指定属性对象,则该对象为NULL,系统会创建具有以下属性的缺省线程:■进程范围■非分离■缺省栈和缺省栈大小■零优先级还可以用pthread_attr_init()创建缺省属性对象,然后使用该属性对象来创建缺省线程。有关详细信息,请参见第52页中的“初始化属性”一节。pthread_create语法使用pthread_create(3C)可以向当前进程中添加新的受控线程。intpthread_create(pthread_t*tid,constpthread_attr_t*tattr,void*(*start_routine)(void*),void*arg);23 线程库#includepthread_attr_t()tattr;pthread_ttid;externvoid*start_routine(void*arg);void*arg;intret;/*defaultbehavior*/ret=pthread_create(&tid,NULL,start_routine,arg);/*initializedwithdefaultattributes*/ret=pthread_attr_init(&tattr);/*defaultbehaviorspecified*/ret=pthread_create(&tid,&tattr,start_routine,arg);使用具有必要状态行为的attr调用pthread_create()函数。start_routine是新线程最先执行的函数。当start_routine返回时,该线程将退出,其退出状态设置为由start_routine返回的值。请参见第23页中的“pthread_create语法”。当pthread_create()成功时,所创建线程的ID被存储在由tid指向的位置中。使用NULL属性参数或缺省属性调用pthread_create()时,pthread_create()会创建一个缺省线程。在对tattr进行初始化之后,该线程将获得缺省行为。pthread_create返回值pthread_create()在调用成功完成之后返回零。其他任何返回值都表示出现了错误。如果检测到以下任一情况,pthread_create()将失败并返回相应的值。EAGAIN描述:超出了系统限制,如创建的线程太多。24多线程编程指南•2006年10月 线程库EINVAL描述:tattr的值无效。等待线程终止pthread_join()函数会一直阻塞调用线程,直到指定的线程终止。pthread_join语法使用pthread_join(3C)等待线程终止。intpthread_join(thread_ttid,void**status);#includepthread_ttid;intret;void*status;/*waitingtojointhread"tid"withstatus*/ret=pthread_join(tid,&status);/*waitingtojointhread"tid"withoutstatus*/ret=pthread_join(tid,NULL);指定的线程必须位于当前的进程中,而且不得是分离线程。有关线程分离的信息,请参见第54页中的“设置分离状态”。当status不是NULL时,status指向某个位置,在pthread_join()成功返回时,将该位置设置为已终止线程的退出状态。如果多个线程等待同一个线程终止,则所有等待线程将一直等到目标线程终止。然后,一个等待线程成功返回。其余的等待线程将失败并返回ESRCH错误。在pthread_join()返回之后,应用程序可回收与已终止线程关联的任何数据存储空间。第2章•基本线程编程25 线程库pthread_join返回值调用成功完成后,pthread_join()将返回零。其他任何返回值都表示出现了错误。如果检测到以下任一情况,pthread_join()将失败并返回相应的值。ESRCH描述:没有找到与给定的线程ID相对应的线程。EDEADLK描述:将出现死锁,如一个线程等待其本身,或者线程A和线程B互相等待。EINVAL描述:与给定的线程ID相对应的线程是分离线程。pthread_join()仅适用于非分离的目标线程。如果没有必要等待特定线程终止之后才进行其他处理,则应当将该线程分离。简单线程的示例在示例2–1中,一个线程执行位于顶部的过程,该过程首先创建一个辅助线程来执行fetch()过程。fetch()执行复杂的数据库查找操作,查找过程需要花费一些时间。主线程将等待查找结果,但同时还执行其他操作。因此,主线程将执行其他活动,然后通过执行pthread_join()等待辅助线程。将新线程的pbe参数作为栈参数进行传递。这个线程参数之所以能够作为栈参数传递,是因为主线程会等待辅助线程终止。不过,首选方法是使用malloc从堆分配存储,而不是传递指向线程栈存储的地址。如果将该参数作为地址传递到线程栈存储,则该地址可能无效或者在线程终止时会被重新分配。示例2–1简单线程程序voidmainline(...){structphonebookentry*pbe;pthread_attr_ttattr;pthread_thelper;void*status;26多线程编程指南•2006年10月 线程库示例2–1简单线程程序(续)pthread_create(&helper,NULL,fetch,&pbe);/*dosomethingelseforawhile*/pthread_join(helper,&status);/*it’snowsafetouseresult*/}void*fetch(structphonebookentry*arg){structphonebookentry*npbe;/*fetchvaluefromadatabase*/npbe=search(prog_name)if(npbe!=NULL)*arg=*npbe;pthread_exit(0);}structphonebookentry{charname[64];charphonenumber[32];charflags[16];第2章•基本线程编程27 线程库示例2–1简单线程程序(续)}分离线程pthread_detach(3C)是pthread_join(3C)的替代函数,可回收创建时detachstate属性设置为PTHREAD_CREATE_JOINABLE的线程的存储空间。pthread_detach语法intpthread_detach(thread_ttid);#includepthread_ttid;intret;/*detachthreadtid*/ret=pthread_detach(tid);pthread_detach()函数用于指示应用程序在线程tid终止时回收其存储空间。如果tid尚未终止,pthread_detach()不会终止该线程。pthread_detach返回值pthread_detach()在调用成功完成之后返回零。其他任何返回值都表示出现了错误。如果检测到以下任一情况,pthread_detach()将失败并返回相应的值。EINVAL描述:tid是分离线程。ESRCH描述:tid不是当前进程中有效的未分离的线程。28多线程编程指南•2006年10月 线程库为线程特定数据创建键单线程C程序有两类基本数据:局部数据和全局数据。对于多线程C程序,添加了第三类数据:线程特定数据。线程特定数据与全局数据非常相似,区别在于前者为线程专有。线程特定数据基于每线程进行维护。TSD(特定于线程的数据)是定义和引用线程专用数据的唯一方法。每个线程特定数据项都与一个作用于进程内所有线程的键关联。通过使用key,线程可以访问基于每线程进行维护的指针(void*)。pthread_key_create语法intpthread_key_create(pthread_key_t*key,void(*destructor)(void*));#includepthread_key_tkey;intret;/*keycreatewithoutdestructor*/ret=pthread_key_create(&key,NULL);/*keycreatewithdestructor*/ret=pthread_key_create(&key,destructor);可以使用pthread_key_create(3C)分配用于标识进程中线程特定数据的键。键对进程中的所有线程来说是全局的。创建线程特定数据时,所有线程最初都具有与该键关联的NULL值。使用各个键之前,会针对其调用一次pthread_key_create()。不存在对键(为进程中所有的线程所共享)的隐含同步。创建键之后,每个线程都会将一个值绑定到该键。这些值特定于线程并且针对每个线程单独维护。如果创建该键时指定了destructor函数,则该线程终止时,系统会解除针对每线程的绑定。当pthread_key_create()成功返回时,会将已分配的键存储在key指向的位置中。调用方必须确保对该键的存储和访问进行正确的同步。第2章•基本线程编程29 线程库使用可选的析构函数destructor可以释放过时的存储。如果某个键具有非NULLdestructor函数,而线程具有一个与该键关联的非NULL值,则该线程退出时,系统将使用当前的相关值调用destructor函数。destructor函数的调用顺序不确定。pthread_key_create返回值pthread_key_create()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_key_create()将失败并返回相应的值。EAGAIN描述:key名称空间已经用完。ENOMEM描述:此进程中虚拟内存不足,无法创建新键。删除线程特定数据键使用pthread_key_delete(3C)可以销毁现有线程特定数据键。由于键已经无效,因此将释放与该键关联的所有内存。引用无效键将返回错误。Solaris线程中没有类似的函数。pthread_key_delete语法intpthread_key_delete(pthread_key_tkey);#includepthread_key_tkey;intret;/*keypreviouslycreated*/ret=pthread_key_delete(key);如果已删除键,则使用调用pthread_setspecific()或pthread_getspecific()引用该键时,生成的结果将是不确定的。程序员在调用删除函数之前必须释放所有线程特定资源。删除函数不会调用任何析构函数。反复调用pthread_key_create()和pthread_key_delete()可能会产生问题。如果pthread_key_delete()将键标记为无效,而之后key的值不再被重用,那么反复调用它们就会出现问题。对于每个所需的键,应当只调用pthread_key_create()一次。30多线程编程指南•2006年10月 线程库pthread_key_delete返回值pthread_key_delete()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,pthread_key_delete()将失败并返回相应的值。EINVAL描述:key的值无效。设置线程特定数据使用pthread_setspecific(3C)可以为指定线程特定数据键设置线程特定绑定。pthread_setspecific语法intpthread_setspecific(pthread_key_tkey,constvoid*value);#includepthread_key_tkey;void*value;intret;/*keypreviouslycreated*/ret=pthread_setspecific(key,value);pthread_setspecific返回值pthread_setspecific()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_setspecific()将失败并返回相应的值。ENOMEM描述:虚拟内存不足。EINVAL描述:key无效。第2章•基本线程编程31 线程库注–设置新绑定时,pthread_setspecific()不会释放其存储空间。必须释放现有绑定,否则会出现内存泄漏。获取线程特定数据请使用pthread_getspecific(3C)获取调用线程的键绑定,并将该绑定存储在value指向的位置中。pthread_getspecific语法void*pthread_getspecific(pthread_key_tkey);#includepthread_key_tkey;void*value;/*keypreviouslycreated*/value=pthread_getspecific(key);pthread_getspecific返回值pthread_getspecific不返回任何错误。全局和专用线程特定数据的示例示例2–2显示的代码是从多线程程序中摘录出来的。这段代码可以由任意数量的线程执行,但该代码引用了两个全局变量:errno和mywindow。这些全局值实际上应当是对每个线程专用项的引用。示例2–2线程特定数据-全局但专用body(){...32多线程编程指南•2006年10月 线程库示例2–2线程特定数据-全局但专用(续)while(write(fd,buffer,size)==-1){if(errno!=EINTR){fprintf(mywindow,"%s ",strerror(errno));exit(1);}}...}errno引用应该从线程所调用的例程获取系统错误,而从其他线程所调用的例程获取系统错误。因此,线程不同,引用errno所指向的存储位置也不同。mywindow变量指向一个stdio(标准IO)流,作为线程专属的流窗口。因此,与errno一样,线程不同,引用mywindow所指向的存储位置也不同。最终,这个引用指向不同的流窗口。唯一的区别在于系统负责处理errno,而程序员必须处理对mywindow的引用。下一个示例说明对mywindow的引用如何工作。预处理程序会将对mywindow的引用转换为对_mywindow()过程的调用。此例程随后调用pthread_getspecific()。pthread_getspecific()接收mywindow_key全局变量作为输入参数,以输出参数win返回该线程的窗口。示例2–3将全局引用转化为专用引用thread_key_tmywin_key;FILE*_mywindow(void){FILE*win;第2章•基本线程编程33 线程库示例2–3将全局引用转化为专用引用(续)win=pthread_getspecific(mywin_key);return(win);}#definemywindow_mywindow()voidroutine_uses_win(FILE*win){...}voidthread_start(...){...make_mywin();...routine_uses_win(mywindow)...}mywin_key变量标识一类变量,对于该类变量,每个线程都有其各自的专用副本。这些变量是线程特定数据。每个线程都调用make_mywin()以初始化其时限并安排其mywindow实例以引用线程特定数据。调用此例程之后,此线程可以安全地引用mywindow,调用_mywindow()之后,此线程将获得对其专用时限的引用。引用mywindow类似于直接引用线程专用数据。示例2–4说明如何设置引用。34多线程编程指南•2006年10月 线程库示例2–4初始化线程特定数据voidmake_mywindow(void){FILE**win;staticpthread_once_tmykeycreated=PTHREAD_ONCE_INIT;pthread_once(&mykeycreated,mykeycreate);win=malloc(sizeof(*win));create_window(win,...);pthread_setspecific(mywindow_key,win);}voidmykeycreate(void){pthread_key_create(&mywindow_key,free_key);}voidfree_key(void*win){free(win);}首先,得到一个唯一的键值,mywin_key。此键用于标识线程特定数据类。第一个调用make_mywin()的线程最终会调用pthread_key_create(),该函数将唯一的key赋给其第一个参数。第二个参数是destructor函数,用于在线程终止后将该线程的特定于该线程的数据项实例解除分配。接下来为调用方的线程特定数据项的实例分配存储空间。获取已分配的存储空间,调用create_window(),以便为该线程设置时限。win指向为该时限分配的存储空间。最后,调用pthread_setspecific(),将win与该键关联。第2章•基本线程编程35 线程库以后,每当线程调用pthread_getspecific()以传递全局key,线程都会获得它在前一次调用pthread_setspecific()时设置的与该键关联的值)。线程终止时,会调用在pthread_key_create()中设置的destructor函数。每个destructor函数仅在终止线程通过调用pthread_setspecific()为key赋值之后才会被调用。获取线程标识符请使用pthread_self(3C)获取调用线程的threadidentifier。pthread_self语法pthread_tpthread_self(void);#includepthread_ttid;tid=pthread_self();pthread_self返回值pthread_self()返回调用线程的threadidentifier。比较线程ID请使用pthread_equal(3C)对两个线程的线程标识号进行比较。pthread_equal语法intpthread_equal(pthread_ttid1,pthread_ttid2);#includepthread_ttid1,tid2;intret;36多线程编程指南•2006年10月 线程库ret=pthread_equal(tid1,tid2);pthread_equal返回值如果tid1和tid2相等,pthread_equal()将返回非零值,否则将返回零。如果tid1或tid2是无效的线程标识号,则结果无法预测。初始化线程使用pthread_once(3C),可以在首次调用pthread_once时调用初始化例程。以后调用pthread_once()将不起作用。pthread_once语法intpthread_once(pthread_once_t*once_control,void(*init_routine)(void));#includepthread_once_tonce_control=PTHREAD_ONCE_INIT;intret;ret=pthread_once(&once_control,init_routine);once_control参数用来确定是否已调用相关的初始化例程。pthread_once返回值pthread_once()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,pthread_once()将失败并返回相应的值。EINVAL描述:once_control或init_routine是NULL。第2章•基本线程编程37 线程库停止执行线程使用sched_yield(3RT),可以使当前线程停止执行,以便执行另一个具有相同或更高优先级的线程。sched_yield语法intsched_yield(void);#includeintret;ret=sched_yield();sched_yield返回值sched_yield()在成功完成之后返回零。否则,返回-1,并设置errno以指示错误状态。ENOSYS描述:本实现不支持sched_yield。设置线程的优先级请使用pthread_setschedparam(3C)修改现有线程的优先级。此函数对于调度策略不起作用。pthread_setschedparam语法intpthread_setschedparam(pthread_ttid,intpolicy,conststructsched_param*param);#includepthread_ttid;intret;38多线程编程指南•2006年10月 线程库structsched_paramparam;intpriority;/*sched_prioritywillbethepriorityofthethread*/sched_param.sched_priority=priority;policy=SCHED_OTHER;/*schedulingparametersoftargetthread*/ret=pthread_setschedparam(tid,policy,¶m);pthread_setschedparam返回值pthread_setschedparam()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_setschedparam()函数将失败并返回相应的值。EINVAL描述:所设置属性的值无效。ENOTSUP描述:尝试将该属性设置为不受支持的值。获取线程的优先级pthread_getschedparam(3C)可用来获取现有线程的优先级。pthread_getschedparam语法intpthread_getschedparam(pthread_ttid,intpolicy,structschedparam*param);#includepthread_ttid;第2章•基本线程编程39 线程库sched_paramparam;intpriority;intpolicy;intret;/*schedulingparametersoftargetthread*/ret=pthread_getschedparam(tid,&policy,¶m);/*sched_prioritycontainsthepriorityofthethread*/priority=param.sched_priority;pthread_getschedparam返回值pthread_getschedparam()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。ESRCH描述:tid指定的值不引用现有的线程。向线程发送信号请使用pthread_kill(3C)向线程发送信号。pthread_kill语法intpthread_kill(thread_ttid,intsig);#include#includeintsig;pthread_ttid;40多线程编程指南•2006年10月 线程库intret;ret=pthread_kill(tid,sig);pthread_kill()将信号sig发送到由tid指定的线程。tid所指定的线程必须与调用线程在同一个进程中。sig参数必须来自signal(5)提供的列表。如果sig为零,将执行错误检查,但并不实际发送信号。此错误检查可用来检查tid的有效性。pthread_kill返回值pthread_kill()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_kill()将失败并返回相应的值。EINVAL描述:sig是无效的信号量。ESRCH描述:当前的进程中找不到tid。访问调用线程的信号掩码请使用pthread_sigmask(3C)更改或检查调用线程的信号掩码。pthread_sigmask语法intpthread_sigmask(inthow,constsigset_t*new,sigset_t*old);#include#includeintret;sigset_told,new;ret=pthread_sigmask(SIG_SETMASK,&new,&old);/*setnewmask*/ret=pthread_sigmask(SIG_BLOCK,&new,&old);/*blockingmask*/第2章•基本线程编程41 线程库ret=pthread_sigmask(SIG_UNBLOCK,&new,&old);/*unblocking*/how用来确定如何更改信号组。how可以为以下值之一:■SIG_BLOCK。向当前的信号掩码中添加new,其中new表示要阻塞的信号组。■SIG_UNBLOCK。从当前的信号掩码中删除new,其中new表示要取消阻塞的信号组。■SIG_SETMASK。将当前的信号掩码替换为new,其中new表示新的信号掩码。当new的值为NULL时,how的值没有意义,线程的信号掩码不发生变化。要查询当前已阻塞的信号,请将NULL值赋给new参数。除非old变量为NULL,否则old指向用来存储以前的信号掩码的空间。pthread_sigmask返回值pthread_sigmask()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,pthread_sigmask()将失败并返回相应的值。EINVAL描述:未定义how的值。安全地Fork请参见第150页中的“解决方案:pthread_atfork”中有关pthread_atfork(3C)的论述。pthread_atfork语法intpthread_atfork(void(*prepare)(void),void(*parent)(void),void(*child)(void));pthread_atfork返回值pthread_atfork()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,pthread_atfork()将失败并返回相应的值。ENOMEM描述:表空间不足,无法记录Fork处理程序地址。终止线程请使用pthread_exit(3C)终止线程。42多线程编程指南•2006年10月 线程库pthread_exit语法voidpthread_exit(void*status);#includevoid*status;pthread_exit(status);/*exitwithstatus*/pthread_exit()函数可用来终止调用线程。将释放所有线程特定数据绑定。如果调用线程尚未分离,则线程ID和status指定的退出状态将保持不变,直到应用程序调用pthread_join()以等待该线程。否则,将忽略status。线程ID可以立即回收。有关线程分离的信息,请参见第54页中的“设置分离状态”。pthread_exit返回值调用线程将终止,退出状态设置为status的内容。结束线程可通过以下方法来终止执行:■从线程的第一个(最外面的)过程返回,使线程启动例程。请参见pthread_create。■调用pthread_exit(),提供退出状态。■使用POSIX取消函数执行终止操作。请参见pthread_cancel()。线程的缺省行为是拖延,直到其他线程通过"joining"拖延线程确认其已死亡。此行为与非分离的缺省pthread_create()属性相同,请参见pthread_detach。join的结果是joining线程得到已终止线程的退出状态,已终止的线程将消失。有一个重要的特殊情况,即当初始线程(即调用main()的线程)从main()调用返回时或调用exit()时,整个进程及其所有的线程将终止。因此,一定要确保初始线程不会从main()过早地返回。请注意,如果主线程仅仅调用了pthread_exit,则仅主线程本身终止。进程及进程内的其他线程将继续存在。所有线程都已终止时,进程也将终止。取消线程取消操作允许线程请求终止其所在进程中的任何其他线程。不希望或不需要对一组相关的线程执行进一步操作时,可以选择执行取消操作。第2章•基本线程编程43 线程库取消线程的一个示例是异步生成取消条件,例如,用户请求关闭或退出正在运行的应用程序。另一个示例是完成由许多线程执行的任务。其中的某个线程可能最终完成了该任务,而其他线程还在继续运行。由于正在运行的线程此时没有任何用处,因此应当取消这些线程。取消点仅当取消操作安全时才应取消线程。pthreads标准指定了几个取消点,其中包括:■通过pthread_testcancel调用以编程方式建立线程取消点。■线程等待pthread_cond_wait或pthread_cond_timedwait(3C)中的特定条件出现。■■被sigwait(2)阻塞的线程。■一些标准的库调用。通常,这些调用包括线程可基于其阻塞的函数。有关列表,请参见cancellation(5)手册页。缺省情况下将启用取消功能。有时,您可能希望应用程序禁用取消功能。如果禁用取消功能,则会导致延迟所有的取消请求,直到再次启用取消请求。有关禁用取消功能的信息,请参见第45页中的“pthread_setcancelstate语法”。放置取消点执行取消操作存在一定的危险。大多数危险都与完全恢复不变量和释放共享资源有关。取消线程时一定要格外小心,否则可能会使互斥保留为锁定状态,从而导致死锁。或者,已取消的线程可能保留已分配的内存区域,但是系统无法识别这一部分内存,从而无法释放它。标准C库指定了一个取消接口用于以编程方式允许或禁止取消功能。该库定义的取消点是一组可能会执行取消操作的点。该库还允许定义取消处理程序的范围,以确保这些处理程序在预期的时间和位置运行。取消处理程序提供的清理服务可以将资源和状态恢复到与起点一致的状态。必须对应用程序有一定的了解,才能放置取消点并执行取消处理程序。互斥肯定不是取消点,只应当在必要时使之保留尽可能短的时间。请将异步取消区域限制在没有外部依赖性的序列,因为外部依赖性可能会产生挂起的资源或未解决的状态条件。在从某个备用的嵌套取消状态返回时,一定要小心地恢复取消状态。该接口提供便于进行恢复的功能:pthread_setcancelstate(3C)在所引用的变量中保留当前的取消状态,pthread_setcanceltype(3C)以同样的方式保留当前的取消类型。在以下三种不同的情况下可能会执行取消操作:■异步■执行序列中按标准定义的各个点44多线程编程指南•2006年10月 线程库■调用pthread_testcancel()时缺省情况下,仅在POSIX标准可靠定义的点执行取消操作。无论何时,都应注意资源和状态恢已复到与起点一致的状态。取消线程请使用pthread_cancel(3C)取消线程。pthread_cancel语法intpthread_cancel(pthread_tthread);#includepthread_tthread;intret;ret=pthread_cancel(thread);取消请求的处理方式取决于目标线程的状态。状态由以下两个函数确定:pthread_setcancelstate(3C)和pthread_setcanceltype(3C)。pthread_cancel返回值pthread_cancel()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。ESRCH描述:没有找到与给定线程ID相对应的线程。启用或禁用取消功能请使用pthread_setcancelstate(3C)启用或禁用线程取消功能。创建线程时,缺省情况下线程取消功能处于启用状态。pthread_setcancelstate语法intpthread_setcancelstate(intstate,int*oldstate);第2章•基本线程编程45 线程库#includeintoldstate;intret;/*enabled*/ret=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&oldstate);/*disabled*/ret=pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&oldstate);pthread_setcancelstate返回值pthread_setcancelstate()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,pthread_setcancelstate()函数将失败并返回相应的值。EINVAL描述:状态不是PTHREAD_CANCEL_ENABLE或PTHREAD_CANCEL_DISABLE。设置取消类型使用pthread_setcanceltype(3C)可以将取消类型设置为延迟或异步模式。pthread_setcanceltype语法intpthread_setcanceltype(inttype,int*oldtype);#includeintoldtype;intret;46多线程编程指南•2006年10月 线程库/*deferredmode*/ret=pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&oldtype);/*asyncmode*/ret=pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&oldtype);创建线程时,缺省情况下会将取消类型设置为延迟模式。在延迟模式下,只能在取消点取消线程。在异步模式下,可以在执行过程中的任意一点取消线程。因此建议不使用异步模式。pthread_setcanceltype返回值pthread_setcanceltype()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:类型不是PTHREAD_CANCEL_DEFERRED或PTHREAD_CANCEL_ASYNCHRONOUS。创建取消点请使用pthread_testcancel(3C)为线程建立取消点。pthread_testcancel语法voidpthread_testcancel(void);#includepthread_testcancel();当线程取消功能处于启用状态且取消类型设置为延迟模式时,pthread_testcancel()函数有效。如果在取消功能处于禁用状态下调用pthread_testcancel(),则该函数不起作用。请务必仅在线程取消操作安全的序列中插入pthread_testcancel()。除通过pthread_testcancel()调用以编程方式建立的取消点以外,pthread标准还指定了几个取消点。有关更多详细信息,请参见第44页中的“取消点”。pthread_testcancel返回值pthread_testcancel()没有返回值。第2章•基本线程编程47 线程库将处理程序推送到栈上使用清理处理程序,可以将状态恢复到与起点一致的状态,其中包括清理已分配的资源和恢复不变量。使用pthread_cleanup_push(3C)和pthread_cleanup_pop(3C)函数可以管理清理处理程序。在程序的同一词法域中可以推送和弹出清理处理程序。推送和弹出操作应当始终匹配,否则会生成编译器错误。pthread_cleanup_push语法请使用pthread_cleanup_push(3C)将清理处理程序推送到清理栈(LIFO)。voidpthread_cleanup_push(void(*routine)(void*),void*args);#include/*pushthehandler"routine"oncleanupstack*/pthread_cleanup_push(routine,arg);pthread_cleanup_push返回值pthread_cleanup_push()没有返回值。从栈中弹出处理程序请使用pthread_cleanup_pop(3C)从清理栈中弹出清理处理程序。pthread_cleanup_pop语法voidpthread_cleanup_pop(intexecute);#include/*popthe"func"outofcleanupstackandexecute"func"*/pthread_cleanup_pop(1);/*popthe"func"andDONTexecute"func"*/48多线程编程指南•2006年10月 线程库pthread_cleanup_pop(0);如果弹出函数中的参数为非零值,则会从栈中删除该处理程序并执行该处理程序。如果该参数为零,则会弹出该处理程序,而不执行它。线程显式或隐式调用pthread_exit(3C)时,或线程接受取消请求时,会使用非零参数有效地调用pthread_cleanup_pop()。pthread_cleanup_pop返回值pthread_cleanup_pop()没有返回值。第2章•基本线程编程49 50 第33章线程属性前面一章介绍了使用缺省属性创建线程的基本原理。本章论述如何在创建线程时设置属性。注–只有pthreads使用属性和取消功能。本章中介绍的API仅适用于POSIX线程。除此之外,Solaris线程和pthreads的功能大致是相同的。有关相似和不同之处的更多信息,请参见第8章。属性对象通过设置属性,可以指定一种不同于缺省行为的行为。使用pthread_create(3C)创建线程时,或初始化同步变量时,可以指定属性对象。缺省值通常就足够了。属性对象是不透明的,而且不能通过赋值直接进行修改。系统提供了一组函数,用于初始化、配置和销毁每种对象类型。初始化和配置属性后,属性便具有进程范围的作用域。使用属性时最好的方法即是在程序执行早期一次配置好所有必需的状态规范。然后,根据需要引用相应的属性对象。使用属性对象具有两个主要优点。■使用属性对象可增加代码可移植性。即使支持的属性可能会在实现之间有所变化,但您不需要修改用于创建线程实体的函数调用。这些函数调用不需要进行修改,因为属性对象是隐藏在接口之后的。如果目标系统支持的属性在当前系统中不存在,则必须显式提供才能管理新的属性。管理这些属性是一项非常容易的移植任务,因为只需在明确定义的位置初始化属性对象一次即可。■应用程序中的状态规范已被简化。51 属性对象例如,假设进程中可能存在多组线程。每组线程都提供单独的服务。每组线程都有各自的状态要求。在应用程序执行初期的某一时间,可以针对每组线程初始化线程属性对象。以后所有线程的创建都会引用已经为这类线程初始化的属性对象。初始化阶段是简单和局部的。将来就可以快速且可靠地进行任何修改。在进程退出时需要注意属性对象。初始化对象时,将为该对象分配内存。必须将此内存返回给系统。pthreads标准提供了用于销毁属性对象的函数调用。初始化属性请使用pthread_attr_init(3C)将对象属性初始化为其缺省值。存储空间是在执行期间由线程系统分配的。pthread_attr_init语法intpthread_attr_init(pthread_attr_t*tattr);#includepthread_attr_ttattr;intret;/*initializeanattributetothedefaultvalue*/ret=pthread_attr_init(&tattr);表3–1给出了属性(tattr)的缺省值。表3–1tattr的缺省属性值属性值结果scopePTHREAD_SCOPE_PROCESS新线程与进程中的其他线程发生竞争。detachstatePTHREAD_CREATE_JOINABLE线程退出后,保留完成状态和线程ID。stackaddrNULL新线程具有系统分配的栈地址。52多线程编程指南•2006年10月 属性对象表3–1tattr的缺省属性值(续)属性值结果stacksize0新线程具有系统定义的栈大小。priority0新线程的优先级为0。inheritschedPTHREAD_EXPLICIT_SCHED新线程不继承父线程调度优先级。schedpolicySCHED_OTHER新线程对同步对象争用使用Solaris定义的固定优先级。线程将一直运行,直到被抢占或者直到线程阻塞或停止为止。pthread_attr_init返回值pthread_attr_init()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。ENOMEM描述:如果未分配足够的内存来初始化线程属性对象,将返回该值。销毁属性请使用pthread_attr_destroy(3C)删除初始化期间分配的存储空间。属性对象将会无效。pthread_attr_destroy语法intpthread_attr_destroy(pthread_attr_t*tattr);#includepthread_attr_ttattr;intret;/*destroyanattribute*/ret=pthread_attr_destroy(&tattr);第3章•线程属性53 属性对象pthread_attr_destroy返回值pthread_attr_destroy()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:指示tattr的值无效。设置分离状态如果创建分离线程(PTHREAD_CREATE_DETACHED),则该线程一退出,便可重用其线程ID和其他资源。如果调用线程不准备等待线程退出,请使用pthread_attr_setdetachstate(3C)。pthread_attr_setdetachstate(3C)语法intpthread_attr_setdetachstate(pthread_attr_t*tattr,intdetachstate);#includepthread_attr_ttattr;intret;/*setthethreaddetachstate*/ret=pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_DETACHED);如果使用PTHREAD_CREATE_JOINABLE创建非分离线程,则假设应用程序将等待线程完成。也就是说,程序将对线程执行pthread_join()。无论是创建分离线程还是非分离线程,在所有线程都退出之前,进程不会退出。有关从main()提前退出而导致的进程终止的讨论,请参见第43页中的“结束”。注–如果未执行显式同步来防止新创建的分离线程失败,则在线程创建者从pthread_create()返回之前,可以将其线程ID重新分配给另一个新线程。非分离线程在终止后,必须要有一个线程用join来等待它。否则,不会释放该线程的资源以供新线程使用,而这通常会导致内存泄漏。因此,如果不希望线程被等待,请将该线程作为分离线程来创建。54多线程编程指南•2006年10月 属性对象示例3–1创建分离线程#includepthread_attr_ttattr;pthread_ttid;void*start_routine;voidargintret;/*initializedwithdefaultattributes*/ret=pthread_attr_init(&tattr);ret=pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_DETACHED);ret=pthread_create(&tid,&tattr,start_routine,arg);pthread_attr_setdetachstate返回值pthread_attr_setdetachstate()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:指示detachstate或tattr的值无效。获取分离状态请使用pthread_attr_getdetachstate(3C)检索线程创建状态(可以为分离或连接)。pthread_attr_getdetachstate语法intpthread_attr_getdetachstate(constpthread_attr_t*tattr,int*detachstate;#include第3章•线程属性55 属性对象pthread_attr_ttattr;intdetachstate;intret;/*getdetachstateofthread*/ret=pthread_attr_getdetachstate(&tattr,&detachstate);pthread_attr_getdetachstate返回值pthread_attr_getdetachstate()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:指示detachstate的值为NULL或tattr无效。设置栈溢出保护区大小pthread_attr_setguardsize(3C)可以设置attr对象的guardsize。pthread_attr_setguardsize(3C)语法#includeintpthread_attr_setguardsize(pthread_attr_t*attr,size_tguardsize);出于以下两个原因,为应用程序提供了guardsize属性:■溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。■线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。guardsize参数提供了对栈指针溢出的保护。如果创建线程的栈时使用了保护功能,则实现会在栈的溢出端分配额外内存。此额外内存的作用与缓冲区一样,可以防止栈指针的栈溢出。如果应用程序溢出到此缓冲区中,这个错误可能会导致SIGSEGV信号被发送给该线程。56多线程编程指南•2006年10月 属性对象如果guardsize为零,则不会为使用attr创建的线程提供溢出保护区。如果guardsize大于零,则会为每个使用attr创建的线程提供大小至少为guardsize字节的溢出保护区。缺省情况下,线程具有实现定义的非零溢出保护区。允许合乎惯例的实现,将guardsize的值向上舍入为可配置的系统变量PAGESIZE的倍数。请参见sys/mman.h中的PAGESIZE。如果实现将guardsize的值向上舍入为PAGESIZE的倍数,则以guardsize(先前调用pthread_attr_setguardsize()时指定的溢出保护区大小)为单位存储对指定attr的pthread_attr_getguardsize()的调用。pthread_attr_setguardsize返回值如果出现以下情况,pthread_attr_setguardsize()将失败:EINVAL描述:参数attr无效,参数guardsize无效,或参数guardsize包含无效值。获取栈溢出保护区大小pthread_attr_getguardsize(3C)可以获取attr对象的guardsize。pthread_attr_getguardsize语法#includeintpthread_attr_getguardsize(constpthread_attr_t*attr,size_t*guardsize);允许一致的实现将guardsize中包含的值向上舍入为可配置系统变量PAGESIZE的倍数。请参见sys/mman.h中的PAGESIZE。如果实现将guardsize的值向上舍入为PAGESIZE的倍数,则以guardsize(先前调用pthread_attr_setguardsize()时指定的溢出保护区大小)为单位存储对指定attr的pthread_attr_getguardsize()的调用。pthread_attr_getguardsize返回值如果出现以下情况,pthread_attr_getguardsize()将失败:EINVAL描述:参数attr无效,参数guardsize无效,或参数guardsize包含无效值。第3章•线程属性57 属性对象设置范围请使用pthread_attr_setscope(3C)建立线程的争用范围(PTHREAD_SCOPE_SYSTEM或PTHREAD_SCOPE_PROCESS)。使用PTHREAD_SCOPE_SYSTEM时,此线程将与系统中的所有线程进行竞争。使用PTHREAD_SCOPE_PROCESS时,此线程将与进程中的其他线程进行竞争。注–只有在给定进程中才能访问这两种线程类型。pthread_attr_setscope语法intpthread_attr_setscope(pthread_attr_t*tattr,intscope);#includepthread_attr_ttattr;intret;/*boundthread*/ret=pthread_attr_setscope(&tattr,PTHREAD_SCOPE_SYSTEM);/*unboundthread*/ret=pthread_attr_setscope(&tattr,PTHREAD_SCOPE_PROCESS);本示例使用三个函数调用:用于初始化属性的调用、用于根据缺省属性设置所有变体的调用,以及用于创建pthreads的调用。#includepthread_attr_tattr;pthread_ttid;voidstart_routine;58多线程编程指南•2006年10月 属性对象voidarg;intret;/*initializedwithdefaultattributes*/ret=pthread_attr_init(&tattr);/*BOUNDbehavior*/ret=pthread_attr_setscope(&tattr,PTHREAD_SCOPE_SYSTEM);ret=pthread_create(&tid,&tattr,start_routine,arg);pthread_attr_setscope返回值pthread_attr_setscope()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:尝试将tattr设置为无效的值。获取范围请使用pthread_attr_getscope(3C)检索线程范围。pthread_attr_getscope语法intpthread_attr_getscope(pthread_attr_t*tattr,int*scope);#includepthread_attr_ttattr;intscope;intret;第3章•线程属性59 属性对象/*getscopeofthread*/ret=pthread_attr_getscope(&tattr,&scope);pthread_attr_getscope返回值pthread_attr_getscope()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:scope的值为NULL或tattr无效。设置线程并行级别针对标准符合性提供了pthread_setconcurrency(3C)。应用程序使用pthread_setconcurrency()通知系统其所需的并发级别。对于Solaris9发行版中引入的线程实现,此接口没有任何作用,所有可运行的线程都将被连接到LWP。pthread_setconcurrency语法#includeintpthread_setconcurrency(intnew_level);pthread_setconcurrency返回值如果出现以下情况,pthread_setconcurrency()将失败:EINVAL描述:new_level指定的值为负数。EAGAIN描述:new_level指定的值将导致系统资源不足。获取线程并行级别pthread_getconcurrency(3C)返回先前调用pthread_setconcurrency()时设置的值。pthread_getconcurrency语法#include60多线程编程指南•2006年10月 属性对象intpthread_getconcurrency(void);如果以前未调用pthread_setconcurrency()函数,则pthread_getconcurrency()将返回零。pthread_getconcurrency返回值pthread_getconcurrency()始终会返回先前调用pthread_setconcurrency()时设置的并发级别。如果从未调用pthread_setconcurrency(),则pthread_getconcurrency()将返回零。设置调度策略请使用pthread_attr_setschedpolicy(3C)设置调度策略。POSIX标准指定SCHED_FIFO(先入先出)、SCHED_RR(循环)或SCHED_OTHER(实现定义的方法)的调度策略属性。pthread_attr_setschedpolicy(3C)语法intpthread_attr_setschedpolicy(pthread_attr_t*tattr,intpolicy);#includepthread_attr_ttattr;intpolicy;intret;/*settheschedulingpolicytoSCHED_OTHER*/ret=pthread_attr_setschedpolicy(&tattr,SCHED_OTHER);■SCHED_FIFO如果调用进程具有有效的用户ID0,则争用范围为系统(PTHREAD_SCOPE_SYSTEM)的先入先出线程属于实时(RT)调度类。如果这些线程未被优先级更高的线程抢占,则会继续处理该线程,直到该线程放弃或阻塞为止。对于具有进程争用范围(PTHREAD_SCOPE_PROCESS))的线程或其调用进程没有有效用户ID0的线程,请使用SCHED_FIFO。SCHED_FIFO基于TS调度类。■SCHED_RR第3章•线程属性61 属性对象如果调用进程具有有效的用户ID0,则争用范围为系统(PTHREAD_SCOPE_SYSTEM))的循环线程属于实时(RT)调度类。如果这些线程未被优先级更高的线程抢占,并且这些线程没有放弃或阻塞,则在系统确定的时间段内将一直执行这些线程。对于具有进程争用范围(PTHREAD_SCOPE_PROCESS)的线程,请使用SCHED_RR(基于TS调度类)。此外,这些线程的调用进程没有有效的用户ID0。SCHED_FIFO和SCHED_RR在POSIX标准中是可选的,而且仅用于实时线程。有关调度的论述,请参见第19页中的“线程调度”一节。pthread_attr_setschedpolicy返回值pthread_attr_setschedpolicy()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:尝试将tattr设置为无效的值。ENOTSUP描述:尝试将该属性设置为不受支持的值。获取调度策略请使用pthread_attr_getschedpolicy(3C)检索调度策略。pthread_attr_getschedpolicy语法intpthread_attr_getschedpolicy(pthread_attr_t*tattr,int*policy);#includepthread_attr_ttattr;intpolicy;intret;/*getschedulingpolicyofthread*/ret=pthread_attr_getschedpolicy(&tattr,&policy);62多线程编程指南•2006年10月 属性对象pthread_attr_getschedpolicy返回值pthread_attr_getschedpolicy()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:参数policy为NULL或tattr无效。设置继承的调度策略请使用pthread_attr_setinheritsched(3C)设置继承的调度策略。pthread_attr_setinheritsched语法intpthread_attr_setinheritsched(pthread_attr_t*tattr,intinherit);#includepthread_attr_ttattr;intinherit;intret;/*usethecurrentschedulingpolicy*/ret=pthread_attr_setinheritsched(&tattr,PTHREAD_EXPLICIT_SCHED);inherit值PTHREAD_INHERIT_SCHED表示新建的线程将继承创建者线程中定义的调度策略。将忽略在pthread_create()调用中定义的所有调度属性。如果使用缺省值PTHREAD_EXPLICIT_SCHED,则将使用pthread_create()调用中的属性。pthread_attr_setinheritsched返回值pthread_attr_setinheritsched()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:尝试将tattr设置为无效的值。ENOTSUP描述:尝试将属性设置为不受支持的值。第3章•线程属性63 属性对象获取继承的调度策略pthread_attr_getinheritsched(3C)将返回由pthread_attr_setinheritsched()设置的调度策略。pthread_attr_getinheritsched语法intpthread_attr_getinheritsched(pthread_attr_t*tattr,int*inherit);#includepthread_attr_ttattr;intinherit;intret;/*getschedulingpolicyandpriorityofthecreatingthread*/ret=pthread_attr_getinheritsched(&tattr,&inherit);pthread_attr_getinheritsched返回值pthread_attr_getinheritsched()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:参数inherit为NULL或tattr无效。设置调度参数pthread_attr_setschedparam(3C)可以设置调度参数。pthread_attr_setschedparam语法intpthread_attr_setschedparam(pthread_attr_t*tattr,conststructsched_param*param);#include64多线程编程指南•2006年10月 属性对象pthread_attr_ttattr;intnewprio;sched_paramparam;newprio=30;/*setthepriority;othersareunchanged*/param.sched_priority=newprio;/*setthenewschedulingparam*/ret=pthread_attr_setschedparam(&tattr,¶m);调度参数是在param结构中定义的。仅支持优先级参数。新创建的线程使用此优先级运行。pthread_attr_setschedparam返回值pthread_attr_setschedparam()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:param的值为NULL或tattr无效。可以采用两种方式之一来管理pthreads优先级:■创建子线程之前,可以设置优先级属性■可以更改父线程的优先级,然后再将该优先级改回来获取调度参数pthread_attr_getschedparam(3C)将返回由pthread_attr_setschedparam()定义的调度参数。pthread_attr_getschedparam语法intpthread_attr_getschedparam(pthread_attr_t*tattr,conststructsched_param*param);第3章•线程属性65 属性对象#includepthread_attr_tattr;structsched_paramparam;intret;/*gettheexistingschedulingparam*/ret=pthread_attr_getschedparam(&tattr,¶m);使用指定的优先级创建线程创建线程之前,可以设置优先级属性。将使用在sched_param结构中指定的新优先级创建子线程。此结构还包含其他调度信息。创建子线程时建议执行以下操作:■获取现有参数■更改优先级■创建子线程■恢复原始优先级创建具有优先级的线程的示例示例3–2给出了使用不同于其父线程优先级的优先级创建子线程的示例。示例3–2创建具有优先级的线程#include#includepthread_attr_ttattr;pthread_ttid;intret;intnewprio=20;66多线程编程指南•2006年10月 属性对象示例3–2创建具有优先级的线程(续)sched_paramparam;/*initializedwithdefaultattributes*/ret=pthread_attr_init(&tattr);/*safetogetexistingschedulingparam*/ret=pthread_attr_getschedparam(&tattr,¶m);/*setthepriority;othersareunchanged*/param.sched_priority=newprio;/*settingthenewschedulingparam*/ret=pthread_attr_setschedparam(&tattr,¶m);/*withnewpriorityspecified*/ret=pthread_create(&tid,&tattr,func,arg);pthread_attr_getschedparam返回值pthread_attr_getschedparam()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:param的值为NULL或tattr无效。设置栈大小pthread_attr_setstacksize(3C)可以设置线程栈大小。第3章•线程属性67 属性对象pthread_attr_setstacksize语法intpthread_attr_setstacksize(pthread_attr_t*tattr,size_tsize);#includepthread_attr_ttattr;size_tsize;intret;size=(PTHREAD_STACK_MIN+0x4000);/*settinganewsize*/ret=pthread_attr_setstacksize(&tattr,size);stacksize属性定义系统分配的栈大小(以字节为单位)。size不应小于系统定义的最小栈大小。有关更多信息,请参见第69页中的“关于栈”。size包含新线程使用的栈的字节数。如果size为零,则使用缺省大小。在大多数情况下,零值最适合。PTHREAD_STACK_MIN是启动线程所需的栈空间量。此栈空间没有考虑执行应用程序代码所需的线程例程要求。pthread_attr_setstacksize返回值pthread_attr_setstacksize()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:size值小于PTHREAD_STACK_MIN,或超出了系统强加的限制,或者tattr无效。获取栈大小pthread_attr_getstacksize(3C)将返回由pthread_attr_setstacksize()设置的栈大小。68多线程编程指南•2006年10月 属性对象pthread_attr_getstacksize语法intpthread_attr_getstacksize(pthread_attr_t*tattr,size_t*size);#includepthread_attr_ttattr;size_tsize;intret;/*gettingthestacksize*/ret=pthread_attr_getstacksize(&tattr,&size);pthread_attr_getstacksize返回值pthread_attr_getstacksize()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:tattr无效。关于栈通常,线程栈是从页边界开始的。任何指定的大小都被向上舍入到下一个页边界。不具备访问权限的页将被附加到栈的溢出端。大多数栈溢出都会导致将SIGSEGV信号发送到违例线程。将直接使用调用方分配的线程栈,而不进行修改。指定栈时,还应使用PTHREAD_CREATE_JOINABLE创建线程。在该线程的pthread_join(3C)调用返回之前,不会释放该栈。在该线程终止之前,不会释放该线程的栈。了解这类线程是否已终止的唯一可靠方式是使用pthread_join(3C)。为线程分配栈空间一般情况下,不需要为线程分配栈空间。系统会为每个线程的栈分配1MB(对于32位系统)或2MB(对于64位系统)的虚拟内存,而不保留任何交换空间。系统将使用mmap()的MAP_NORESERVE选项来进行分配。第3章•线程属性69 属性对象系统创建的每个线程栈都具有红色区域。系统通过将页附加到栈的溢出端来创建红色区域,从而捕获栈溢出。此类页无效,而且会导致内存(访问时)故障。红色区域将被附加到所有自动分配的栈,无论大小是由应用程序指定,还是使用缺省大小。注–对于库调用和动态链接,运行时栈要求有所变化。应绝对确定,指定的栈满足库调用和动态链接的运行时要求。极少数情况下需要指定栈和/或栈大小。甚至专家也很难了解是否指定了正确的大小。甚至符合ABI标准的程序也不能静态确定其栈大小。栈大小取决于执行中特定运行时环境的需要。生成自己的栈指定线程栈大小时,必须考虑被调用函数以及每个要调用的后续函数的分配需求。需要考虑的因素应包括调用序列需求、局部变量和信息结构。有时,您需要与缺省栈略有不同的栈。典型的情况是,线程需要的栈大小大于缺省栈大小。而不太典型的情况是,缺省大小太大。您可能正在使用不足的虚拟内存创建数千个线程,进而处理数千个缺省线程栈所需的数千兆字节的栈空间。对栈的最大大小的限制通常较为明显,但对其最小大小的限制如何呢?必须存在足够的栈空间来处理推入栈的所有栈帧,及其局部变量等。要获取对栈大小的绝对最小限制,请调用宏PTHREAD_STACK_MIN。PTHREAD_STACK_MIN宏将针对执行NULL过程的线程返回所需的栈空间量。有用的线程所需的栈大小大于最小栈大小,因此缩小栈大小时应非常谨慎。#includepthread_attr_ttattr;pthread_ttid;intret;size_tsize=PTHREAD_STACK_MIN+0x4000;/*initializedwithdefaultattributes*/70多线程编程指南•2006年10月 属性对象ret=pthread_attr_init(&tattr);/*settingthesizeofthestackalso*/ret=pthread_attr_setstacksize(&tattr,size);/*onlysizespecifiedintattr*/ret=pthread_create(&tid,&tattr,start_routine,arg);设置栈地址和大小pthread_attr_setstack(3C)可以设置线程栈地址和大小。pthread_attr_setstack(3C)语法intpthread_attr_setstack(pthread_attr_t*tattr,void*stackaddr,size_tstacksize);#includepthread_attr_ttattr;void*base;size_tsize;intret;base=(void*)malloc(PTHREAD_STACK_MIN+0x4000);/*settinganewaddressandsize*/ret=pthread_attr_setstack(&tattr,base,PTHREAD_STACK_MIN+0x4000);第3章•线程属性71 属性对象stackaddr属性定义线程栈的基准(低位地址)。stacksize属性指定栈的大小。如果将stackaddr设置为非空值,而不是缺省的NULL,则系统将在该地址初始化栈,假设大小为stacksize。base包含新线程使用的栈的地址。如果base为NULL,则pthread_create(3C)将为大小至少为stacksize字节的新线程分配栈。pthread_attr_setstack(3C)返回值pthread_attr_setstack()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:base或tattr的值不正确。stacksize的值小于PTHREAD_STACK_MIN。以下示例说明如何使用自定义栈地址和大小来创建线程。#includepthread_attr_ttattr;pthread_ttid;intret;void*stackbase;size_tsize;/*initializedwithdefaultattributes*/ret=pthread_attr_init(&tattr);/*settingthebaseaddressandsizeofthestack*/ret=pthread_attr_setstack(&tattr,stackbase,size);/*addressandsizespecified*/ret=pthread_create(&tid,&tattr,func,arg);72多线程编程指南•2006年10月 属性对象获取栈地址和大小pthread_attr_getstack(3C)将返回由pthread_attr_setstack()设置的线程栈地址和大小。pthread_attr_getstack语法intpthread_attr_getstack(pthread_attr_t*tattr,void**stackaddr,size_t*stacksize);#includepthread_attr_ttattr;void*base;size_tsize;intret;/*gettingastackaddressandsize*/ret=pthread_attr_getstackaddr(&tattr,&base,&size);pthread_attr_getstack返回值pthread_attr_getstackaddr()成功完成后将返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:tattr的值不正确。第3章•线程属性73 74 第44章用同步对象编程本章介绍了可用于线程的同步类型,还说明了使用同步的时间和方法。■第76页中的“互斥锁属性”■第91页中的“使用互斥锁”■第105页中的“条件变量属性”■第109页中的“使用条件变量”■第124页中的“使用信号进行同步”■第133页中的“读写锁属性”■第82页中的“设置互斥锁属性的协议”■第142页中的“跨进程边界同步”■第145页中的“比较元语”同步对象是内存中的变量,可以按照与访问数据完全相同的方式对其进行访问。不同进程中的线程可以通过放在由线程控制的共享内存中的同步对象互相通信。尽管不同进程中的线程通常互不可见,但这些线程仍可以互相通信。同步对象还可以放在文件中。同步对象可以比创建它的进程具有更长的生命周期。同步对象具有以下可用类型:■互斥锁■条件变量■读写锁■信号同步的作用包括以下方面:■同步是确保共享数据一致性的唯一方法。■两个或多个进程中的线程可以合用一个同步对象。由于重新初始化同步对象会将对象的状态设置为解除锁定,因此应仅由其中的一个协作进程来初始化同步对象。■同步可确保可变数据的安全性。■进程可以映射文件并指示该进程中的线程获取记录锁。一旦获取了记录锁,映射此文件的任何进程中尝试获取该锁的任何线程都会被阻塞,直到释放该锁为止。75 互斥锁属性■访问一个基本类型变量(如整数)时,可以针对一个内存负荷使用多个存储周期。如果整数没有与总线数据宽度对齐或者大于数据宽度,则会使用多个存储周期。尽®管这种整数不对齐现象不会出现在SPARCPlatformEdition体系结构上,但是可移植的程序却可能会出现对齐问题。注–在32位体系结构上,longlong不是原子类型。(原子操作不能划分成更小的操作。)longlong可作为两个32位值进行读写。类型int、char、float和指针在SPARCPlatformEdition计算机和Intel体系结构的计算机上是原子类型。互斥锁属性使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码。要更改缺省的互斥锁属性,可以对属性对象进行声明和初始化。通常,互斥锁属性会设置在应用程序开头的某个位置,以便可以快速查找和轻松修改。表4–1列出了用来处理互斥锁属性的函数。表4–1互斥锁属性例程操作相关函数说明初始化互斥锁属性对象第77页中的“pthread_mutexattr_init语法”销毁互斥锁属性对象第78页中的“pthread_mutexattr_destroy语法”设置互斥锁范围第79页中的“pthread_mutexattr_setpshared语法”获取互斥锁范围第80页中的“pthread_mutexattr_getpshared语法”设置互斥锁的类型属性第80页中的“pthread_mutexattr_settype语法”获取互斥锁的类型属性第82页中的“pthread_mutexattr_gettype语法”设置互斥锁属性的协议第82页中的“pthread_mutexattr_setprotocol语法”获取互斥锁属性的协议第84页中的“pthread_mutexattr_getprotocol语法”设置互斥锁属性的优先级上限第85页中的“pthread_mutexattr_setprioceiling语法”获取互斥锁属性的优先级上限第86页中的“pthread_mutexattr_getprioceiling语法”设置互斥锁的优先级上限第87页中的“pthread_mutex_setprioceiling语法”获取互斥锁的优先级上限第88页中的“pthread_mutex_getprioceiling语法”76多线程编程指南•2006年10月 互斥锁属性表4–1互斥锁属性例程(续)操作相关函数说明设置互斥锁的强健属性第89页中的“pthread_mutexattr_setrobust_np语法”获取互斥锁的强健属性第91页中的“pthread_mutexattr_getrobust_np语法”表4–2中显示了在定义互斥范围时Solaris线程和POSIX线程之间的差异。表4–2互斥锁范围比较SolarisPOSIX定义USYNC_PROCESSPTHREAD_PROCESS_SHARED用于同步该进程和其他进程中的线程USYNC_PROCESS_ROBUST无POSIX等效项用于在进程间可靠地同步线程USYNC_THREADPTHREAD_PROCESS_PRIVATE用于仅同步该进程中的线程初始化互斥锁属性对象使用pthread_mutexattr_init(3C)可以将与互斥锁对象相关联的属性初始化为其缺省值。在执行过程中,线程系统会为每个属性对象分配存储空间。pthread_mutexattr_init语法intpthread_mutexattr_init(pthread_mutexattr_t*mattr);#includepthread_mutexattr_tmattr;intret;/*initializeanattributetodefaultvalue*/ret=pthread_mutexattr_init(&mattr);调用此函数时,pshared属性的缺省值为PTHREAD_PROCESS_PRIVATE。该值表示可以在进程内使用经过初始化的互斥锁。第4章•用同步对象编程77 互斥锁属性mattr的类型为opaque,其中包含一个由系统分配的属性对象。mattr范围可能的值为PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。PTHREAD_PROCESS_PRIVATE是缺省值。对于互斥锁属性对象,必须首先通过调用pthread_mutexattr_destroy(3C)将其销毁,才能重新初始化该对象。pthread_mutexattr_init()调用会导致分配类型为opaque的对象。如果未销毁该对象,则会导致内存泄漏。pthread_mutexattr_init返回值pthread_mutexattr_init()成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。ENOMEM描述:内存不足,无法初始化互斥锁属性对象。销毁互斥锁属性对象pthread_mutexattr_destroy(3C)可用来取消分配用于维护pthread_mutexattr_init()所创建的属性对象的存储空间。pthread_mutexattr_destroy语法intpthread_mutexattr_destroy(pthread_mutexattr_t*mattr)#includepthread_mutexattr_tmattr;intret;/*destroyanattribute*/ret=pthread_mutexattr_destroy(&mattr);pthread_mutexattr_destroy返回值pthread_mutexattr_destroy()成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:由mattr指定的值无效。78多线程编程指南•2006年10月 互斥锁属性设置互斥锁的范围pthread_mutexattr_setpshared(3C)可用来设置互斥锁变量的作用域。pthread_mutexattr_setpshared语法intpthread_mutexattr_setpshared(pthread_mutexattr_t*mattr,intpshared);#includepthread_mutexattr_tmattr;intret;ret=pthread_mutexattr_init(&mattr);/**resettingtoitsdefaultvalue:private*/ret=pthread_mutexattr_setpshared(&mattr,PTHREAD_PROCESS_PRIVATE);互斥锁变量可以是进程专用的(进程内)变量,也可以是系统范围内的(进程间)变量。要在多个进程中的线程之间共享互斥锁,可以在共享内存中创建互斥锁,并将pshared属性设置为PTHREAD_PROCESS_SHARED。此行为与最初的Solaris线程实现中mutex_init()中的USYNC_PROCESS标志等效。如果互斥锁的pshared属性设置为PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建的线程才能够处理该互斥锁。pthread_mutexattr_setpshared返回值pthread_mutexattr_setpshared()成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:由mattr指定的值无效。第4章•用同步对象编程79 互斥锁属性获取互斥锁的范围pthread_mutexattr_getpshared(3C)可用来返回由pthread_mutexattr_setpshared()定义的互斥锁变量的范围。pthread_mutexattr_getpshared语法intpthread_mutexattr_getpshared(pthread_mutexattr_t*mattr,int*pshared);#includepthread_mutexattr_tmattr;intpshared,ret;/*getpsharedofmutex*/ret=pthread_mutexattr_getpshared(&mattr,&pshared);此函数可为属性对象mattr获取pshared的当前值。该值为PTHREAD_PROCESS_SHARED或PTHREAD_PROCESS_PRIVATE。pthread_mutexattr_getpshared返回值pthread_mutexattr_getpshared()成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:由mattr指定的值无效。设置互斥锁类型的属性pthread_mutexattr_settype(3C)可用来设置互斥锁的type属性。pthread_mutexattr_settype语法#includeintpthread_mutexattr_settype(pthread_mutexattr_t*attr,inttype);80多线程编程指南•2006年10月 互斥锁属性类型属性的缺省值为PTHREAD_MUTEX_DEFAULT。type参数指定互斥锁的类型。以下列出了有效的互斥锁类型:PTHREAD_MUTEX_NORMAL描述:此类型的互斥锁不会检测死锁。如果线程在不首先解除互斥锁的情况下尝试重新锁定该互斥锁,则会产生死锁。尝试解除由其他线程锁定的互斥锁会产生不确定的行为。如果尝试解除锁定的互斥锁未锁定,则会产生不确定的行为。PTHREAD_MUTEX_ERRORCHECK描述:此类型的互斥锁可提供错误检查。如果线程在不首先解除锁定互斥锁的情况下尝试重新锁定该互斥锁,则会返回错误。如果线程尝试解除锁定的互斥锁已经由其他线程锁定,则会返回错误。如果线程尝试解除锁定的互斥锁未锁定,则会返回错误。PTHREAD_MUTEX_RECURSIVE描述:如果线程在不首先解除锁定互斥锁的情况下尝试重新锁定该互斥锁,则可成功锁定该互斥锁。与PTHREAD_MUTEX_NORMAL类型的互斥锁不同,对此类型互斥锁进行重新锁定时不会产生死锁情况。多次锁定互斥锁需要进行相同次数的解除锁定才可以释放该锁,然后其他线程才能获取该互斥锁。如果线程尝试解除锁定的互斥锁已经由其他线程锁定,则会返回错误。如果线程尝试解除锁定的互斥锁未锁定,则会返回错误。PTHREAD_MUTEX_DEFAULT描述:如果尝试以递归方式锁定此类型的互斥锁,则会产生不确定的行为。对于不是由调用线程锁定的此类型互斥锁,如果尝试对它解除锁定,则会产生不确定的行为。对于尚未锁定的此类型互斥锁,如果尝试对它解除锁定,也会产生不确定的行为。允许在实现中将该互斥锁映射到其他互斥锁类型之一。对于Solaris线程,PTHREAD_PROCESS_DEFAULT会映射到PTHREAD_PROCESS_NORMAL。pthread_mutexattr_settype返回值如果运行成功,pthread_mutexattr_settype函数会返回零。否则,将返回用于指明错误的错误号。EINVAL描述:值为type无效。EINVAL描述:attr指定的值无效。第4章•用同步对象编程81 互斥锁属性获取互斥锁的类型属性pthread_mutexattr_gettype(3C)可用来获取由pthread_mutexattr_settype()设置的互斥锁的type属性。pthread_mutexattr_gettype语法#includeintpthread_mutexattr_gettype(pthread_mutexattr_t*attr,int*type);类型属性的缺省值为PTHREAD_MUTEX_DEFAULT。type参数指定互斥锁的类型。有效的互斥锁类型包括:■PTHREAD_MUTEX_NORMAL■PTHREAD_MUTEX_ERRORCHECK■PTHREAD_MUTEX_RECURSIVE■PTHREAD_MUTEX_DEFAULT有关每种类型的说明,请参见第80页中的“pthread_mutexattr_settype语法”。pthread_mutexattr_gettype返回值如果成功完成,pthread_mutexattr_gettype()会返回0。其他任何返回值都表示出现了错误。设置互斥锁属性的协议pthread_mutexattr_setprotocol(3C)可用来设置互斥锁属性对象的协议属性。pthread_mutexattr_setprotocol语法#includeintpthread_mutexattr_setprotocol(pthread_mutexattr_t*attr,intprotocol);attr指示以前调用pthread_mutexattr_init()时创建的互斥锁属性对象。protocol可定义应用于互斥锁属性对象的协议。pthread.h中定义的protocol可以是以下值之一:PTHREAD_PRIO_NONE、PTHREAD_PRIO_INHERIT或PTHREAD_PRIO_PROTECT。82多线程编程指南•2006年10月 互斥锁属性■PTHREAD_PRIO_NONE线程的优先级和调度不会受到互斥锁拥有权的影响。■PTHREAD_PRIO_INHERIT此协议值(如thrd1)会影响线程的优先级和调度。如果更高优先级的线程因thrd1所拥有的一个或多个互斥锁而被阻塞,而这些互斥锁是用PTHREAD_PRIO_INHERIT初始化的,则thrd1将以高于它的优先级或者所有正在等待这些互斥锁(这些互斥锁是thrd1指所拥有的互斥锁)的线程的最高优先级运行。如果thrd1因另一个线程(thrd3)拥有的互斥锁而被阻塞,则相同的优先级继承效应会以递归方式传播给thrd3。使用PTHREAD_PRIO_INHERIT可以避免优先级倒置。低优先级的线程持有较高优先级线程所需的锁时,便会发生优先级倒置。只有在较低优先级的线程释放该锁之后,较高优先级的线程才能继续使用该锁。设置PTHREAD_PRIO_INHERIT,以便按与预期的优先级相反的优先级处理每个线程。如果为使用协议属性值PTHREAD_PRIO_INHERIT初始化的互斥锁定义了_POSIX_THREAD_PRIO_INHERIT,则互斥锁的属主失败时会执行以下操作。属主失败时的行为取决于pthread_mutexattr_setrobust_np()的robustness参数的值。■解除锁定互斥锁。■互斥锁的下一个属主将获取该互斥锁,并返回错误EOWNERDEAD。■互斥锁的下一个属主会尝试使该互斥锁所保护的状态一致。如果上一个属主失败,则状态可能会不一致。如果属主成功使状态保持一致,则可针对该互斥锁调用pthread_mutex_init()并解除锁定该互斥锁。注–如果针对以前初始化的但尚未销毁的互斥锁调用pthread_mutex_init(),则该互斥锁不会重新初始化。■如果属主无法使状态保持一致,请勿调用pthread_mutex_init(),而是解除锁定该互斥锁。在这种情况下,所有等待的线程都将被唤醒。以后对pthread_mutex_lock()的所有调用将无法获取互斥锁,并将返回错误代码ENOTRECOVERABLE。现在,通过调用pthread_mutex_destroy()来取消初始化该互斥锁,即可使其状态保持一致。调用pthread_mutex_init()可重新初始化互斥锁。■如果已获取该锁的线程失败并返回EOWNERDEAD,则下一个属主将获取该锁及错误代码EOWNERDEAD。■PTHREAD_PRIO_PROTECT当线程拥有一个或多个使用PTHREAD_PRIO_PROTECT初始化的互斥锁时,此协议值会影响其他线程(如thrd2)的优先级和调度。thrd2以其较高的优先级或者以thrd2拥有的所有互斥锁的最高优先级上限运行。基于被thrd2拥有的任一互斥锁阻塞的较高优先级线程对于thrd2的调度没有任何影响。第4章•用同步对象编程83 互斥锁属性如果某个线程调用sched_setparam()来更改初始优先级,则调度程序不会采用新优先级将该线程移到调度队列末尾。■线程拥有使用PTHREAD_PRIO_INHERIT或PTHREAD_PRIO_PROTECT初始化的互斥锁■线程解除锁定使用PTHREAD_PRIO_INHERIT或PTHREAD_PRIO_PROTECT初始化的互斥锁一个线程可以同时拥有多个混合使用PTHREAD_PRIO_INHERIT和PTHREAD_PRIO_PROTECT初始化的互斥锁。在这种情况下,该线程将以通过其中任一协议获取的最高优先级执行。pthread_mutexattr_setprotocol返回值如果成功完成,pthread_mutexattr_setprotocol()会返回0。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_mutexattr_setprotocol()将失败并返回对应的值。ENOSYS描述:选项_POSIX_THREAD_PRIO_INHERIT和_POSIX_THREAD_PRIO_PROTECT均未定义并且该实现不支持此函数。ENOTSUP描述:protocol指定的值不受支持。如果出现以下任一情况,pthread_mutexattr_setprotocol()可能会失败并返回对应的值。EINVAL描述:attr或protocol指定的值无效。EPERM描述:调用方无权执行该操作。获取互斥锁属性的协议pthread_mutexattr_getprotocol(3C)可用来获取互斥锁属性对象的协议属性。pthread_mutexattr_getprotocol语法#includeintpthread_mutexattr_getprotocol(constpthread_mutexattr_t*attr,int*protocol);84多线程编程指南•2006年10月 互斥锁属性attr指示以前调用pthread_mutexattr_init()时创建的互斥锁属性对象。protocol包含以下协议属性之一:PTHREAD_PRIO_NONE、PTHREAD_PRIO_INHERIT或PTHREAD_PRIO_PROTECT。pthread_mutexattr_getprotocol返回值如果成功完成,pthread_mutexattr_getprotocol()会返回0。其他任何返回值都表示出现了错误。如果出现以下情况,pthread_mutexattr_getprotocol()将失败并返回对应的值。ENOSYS描述:_POSIX_THREAD_PRIO_INHERIT选项和_POSIX_THREAD_PRIO_PROTECT选项均未定义并且该实现不支持此函数。如果出现以下任一情况,pthread_mutexattr_getprotocol()可能会失败并返回对应的值。EINVAL描述:attr指定的值无效。EPERM描述:调用方无权执行该操作。设置互斥锁属性的优先级上限pthread_mutexattr_setprioceiling(3C)可用来设置互斥锁属性对象的优先级上限属性。pthread_mutexattr_setprioceiling语法#includeintpthread_mutexattr_setprioceiling(pthread_mutexatt_t*attr,intprioceiling,int*oldceiling);attr指示以前调用pthread_mutexattr_init()时创建的互斥锁属性对象。prioceiling指定已初始化互斥锁的优先级上限。优先级上限定义执行互斥锁保护的临界段时的最低优先级。prioceiling位于SCHED_FIFO所定义的优先级的最大范围内。要避免优先级倒置,请将prioceiling设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。第4章•用同步对象编程85 互斥锁属性oldceiling包含以前的优先级上限值。pthread_mutexattr_setprioceiling返回值如果成功完成,pthread_mutexattr_setprioceiling()会返回0。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_mutexattr_setprioceiling()将失败并返回对应的值。ENOSYS描述:选项_POSIX_THREAD_PRIO_PROTECT未定义并且该实现不支持此函数。如果出现以下任一情况,pthread_mutexattr_setprioceiling()可能会失败并返回对应的值。EINVAL描述:attr或prioceiling指定的值无效。EPERM描述:调用方无权执行该操作。获取互斥锁属性的优先级上限pthread_mutexattr_getprioceiling(3C)可用来获取互斥锁属性对象的优先级上限属性。pthread_mutexattr_getprioceiling语法#includeintpthread_mutexattr_getprioceiling(constpthread_mutexatt_t*attr,int*prioceiling);attr指定以前调用pthread_mutexattr_init()时创建的属性对象。注–仅当定义了_POSIX_THREAD_PRIO_PROTECT符号时,attr互斥锁属性对象才会包括优先级上限属性。pthread_mutexattr_getprioceiling()返回prioceiling中已初始化互斥锁的优先级上限。优先级上限定义执行互斥锁保护的临界段时的最低优先级。prioceiling位于86多线程编程指南•2006年10月 互斥锁属性SCHED_FIFO所定义的优先级的最大范围内。要避免优先级倒置,请将prioceiling设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。pthread_mutexattr_getprioceiling返回值如果成功完成,pthread_mutexattr_getprioceiling()会返回0。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_mutexattr_getprioceiling()将失败并返回对应的值。ENOSYS描述:_POSIX_THREAD_PRIO_PROTECT选项未定义并且该实现不支持此函数。如果出现以下任一情况,pthread_mutexattr_getprioceiling()可能会失败并返回对应的值。EINVAL描述:attr指定的值无效。EPERM描述:调用方无权执行该操作。设置互斥锁的优先级上限pthread_mutexattr_setprioceiling(3C)可用来设置互斥锁的优先级上限。pthread_mutex_setprioceiling语法#includeintpthread_mutex_setprioceiling(pthread_mutex_t*mutex,intprioceiling,int*old_ceiling);pthread_mutex_setprioceiling()可更改互斥锁mutex的优先级上限prioceiling。pthread_mutex_setprioceiling()可锁定互斥锁(如果未锁定的话),或者一直处于阻塞状态,直到pthread_mutex_setprioceiling()成功锁定该互斥锁,更改该互斥锁的优先级上限并将该互斥锁释放为止。锁定互斥锁的过程无需遵循优先级保护协议。如果pthread_mutex_setprioceiling()成功,则将在old_ceiling中返回以前的优先级上限值。如果pthread_mutex_setprioceiling()失败,则互斥锁的优先级上限保持不变。第4章•用同步对象编程87 互斥锁属性pthread_mutex_setprioceiling返回值如果成功完成,pthread_mutex_setprioceiling()会返回0。其他任何返回值都表示出现了错误。如果出现以下情况,pthread_mutexatt_setprioceiling()将失败并返回对应的值。ENOSYS描述:选项_POSIX_THREAD_PRIO_PROTECT未定义并且该实现不支持此函数。如果出现以下任一情况,pthread_mutex_setprioceiling()可能会失败并返回对应的值。EINVAL描述:prioceiling所请求的优先级超出了范围。EINVAL描述:mutex指定的值不会引用当前存在的互斥锁。ENOSYS描述:该实现不支持互斥锁的优先级上限协议。EPERM描述:调用方无权执行该操作。获取互斥锁的优先级上限pthread_mutexattr_getprioceiling(3C)可用来获取互斥锁的优先级上限。pthread_mutex_getprioceiling语法#includeintpthread_mutex_getprioceiling(constpthread_mutex_t*mutex,int*prioceiling);pthread_mutex_getprioceiling()会返回mutex的优先级上限prioceiling。pthread_mutex_getprioceiling返回值如果成功完成,pthread_mutex_getprioceiling()会返回0。其他任何返回值都表示出现了错误。88多线程编程指南•2006年10月 互斥锁属性如果出现以下任一情况,pthread_mutexatt_getprioceiling()将失败并返回对应的值。ENOSYS描述:_POSIX_THREAD_PRIO_PROTECT选项未定义并且该实现不支持此函数。如果出现以下任一情况,pthread_mutex_getprioceiling()可能会失败并返回对应的值。EINVAL描述:mutex指定的值不会引用当前存在的互斥锁。ENOSYS描述:该实现不支持互斥锁的优先级上限协议。EPERM描述:调用方无权执行该操作。设置互斥锁的强健属性pthread_mutexattr_setrobust_np(3C)可用来设置互斥锁属性对象的强健属性。pthread_mutexattr_setrobust_np语法#includeintpthread_mutexattr_setrobust_np(pthread_mutexattr_t*attr,int*robustness);注–仅当定义了符号_POSIX_THREAD_PRIO_INHERIT时,pthread_mutexattr_setrobust_np()才适用。attr指示以前通过调用pthread_mutexattr_init()创建的互斥锁属性对象。robustness定义在互斥锁的属主失败时的行为。pthread.h中定义的robustness的值为PTHREAD_MUTEX_ROBUST_NP或PTHREAD_MUTEX_STALLED_NP。缺省值为PTHREAD_MUTEX_STALLED_NP。■PTHREAD_MUTEX_ROBUST_NP如果互斥锁的属主失败,则以后对pthread_mutex_lock()的所有调用将以不确定的方式被阻塞。第4章•用同步对象编程89 互斥锁属性■PTHREAD_MUTEX_STALLED_NP互斥锁的属主失败时,将会解除锁定该互斥锁。互斥锁的下一个属主将获取该互斥锁,并返回错误EOWNWERDEAD。注–应用程序必须检查pthread_mutex_lock()的返回代码,查找返回错误EOWNWERDEAD的互斥锁。■互斥锁的新属主应使该互斥锁所保护的状态保持一致。如果上一个属主失败,则互斥锁状态可能会不一致。■如果新属主能够使状态保持一致,请针对该互斥锁调用pthread_mutex_consistent_np(),并解除锁定该互斥锁。■如果新属主无法使状态保持一致,请勿针对该互斥锁调用pthread_mutex_consistent_np(),而是解除锁定该互斥锁。所有等待的线程都将被唤醒,以后对pthread_mutex_lock()的所有调用都将无法获取该互斥锁。返回代码为ENOTRECOVERABLE。通过调用pthread_mutex_destroy()取消对互斥锁的初始化,并调用pthread_mutex_int()重新初始化该互斥锁,可使该互斥锁保持一致。如果已获取该锁的线程失败并返回EOWNERDEAD,则下一个属主获取该锁时将返回代码EOWNERDEAD。pthread_mutexattr_setrobust_np返回值如果成功完成,pthread_mutexattr_setrobust_np()会返回0。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_mutexattr_setrobust_np()将失败并返回对应的值。ENOSYS描述:选项_POSIX_THREAD_PRIO__INHERIT未定义,或者该实现不支持pthread_mutexattr_setrobust_np()。ENOTSUP描述:robustness指定的值不受支持。pthread_mutexattr_setrobust_np()可能会在出现以下情况时失败:EINVAL描述:attr或robustness指定的值无效。获取互斥锁的强健属性pthread_mutexattr_getrobust_np(3C)可用来获取互斥锁属性对象的强健属性。90多线程编程指南•2006年10月 使用互斥锁pthread_mutexattr_getrobust_np语法#includeintpthread_mutexattr_getrobust_np(constpthread_mutexattr_t*attr,int*robustness);注–仅当定义了符号_POSIX_THREAD_PRIO_INHERIT时,pthread_mutexattr_getrobust_np()才适用。attr指示以前通过调用pthread_mutexattr_init()创建的互斥锁属性对象。robustness是互斥锁属性对象的强健属性值。pthread_mutexattr_getrobust_np返回值如果成功完成,pthread_mutexattr_getrobust_np()会返回0。其他任何返回值都表示出现了错误。如果出现以下任一情况,pthread_mutexattr_getrobust_np()将失败并返回对应的值。ENOSYS描述:选项_POSIX_THREAD_PRIO__INHERIT未定义,或者该实现不支持pthread_mutexattr_getrobust_np()。pthread_mutexattr_getrobust_np()可能会在出现以下情况时失败:EINVAL描述:attr或robustness指定的值无效。使用互斥锁表4–3列出了用来处理互斥锁的函数。表4–3互斥锁的例程操作相关函数说明初始化互斥锁第92页中的“pthread_mutex_init语法”使互斥锁保持一致第93页中的“pthread_mutex_consistent_np语法”第4章•用同步对象编程91 使用互斥锁表4–3互斥锁的例程(续)操作相关函数说明锁定互斥锁第94页中的“pthread_mutex_lock语法”解除锁定互斥锁第96页中的“pthread_mutex_unlock语法”使用非阻塞互斥锁锁定第97页中的“pthread_mutex_trylock语法”销毁互斥锁第98页中的“pthread_mutex_destroy语法”缺省调度策略SCHED_OTHER不指定线程可以获取锁的顺序。如果多个线程正在等待一个互斥锁,则获取顺序是不确定的。出现争用时,缺省行为是按优先级顺序解除线程的阻塞。初始化互斥锁使用pthread_mutex_init(3C)可以使用缺省值初始化由mp所指向的互斥锁,还可以指定已经使用pthread_mutexattr_init()设置的互斥锁属性。mattr的缺省值为NULL。对于Solaris线程,请参见第210页中的“mutex_init(3C)语法”。pthread_mutex_init语法intpthread_mutex_init(pthread_mutex_t*mp,constpthread_mutexattr_t*mattr);#includepthread_mutex_tmp=PTHREAD_MUTEX_INITIALIZER;pthread_mutexattr_tmattr;intret;/*initializeamutextoitsdefaultvalue*/ret=pthread_mutex_init(&mp,NULL);/*initializeamutex*/ret=pthread_mutex_init(&mp,&mattr);92多线程编程指南•2006年10月 使用互斥锁如果互斥锁已初始化,则它会处于未锁定状态。互斥锁可以位于进程之间共享的内存中或者某个进程的专用内存中。注–初始化互斥锁之前,必须将其所在的内存清零。将mattr设置为NULL的效果与传递缺省互斥锁属性对象的地址相同,但是没有内存开销。使用PTHREAD_MUTEX_INITIALIZER宏可以将以静态方式定义的互斥锁初始化为其缺省属性。当其他线程正在使用某个互斥锁时,请勿重新初始化或销毁该互斥锁。如果任一操作没有正确完成,将会导致程序失败。如果要重新初始化或销毁某个互斥锁,则应用程序必须确保当前未使用该互斥锁。pthread_mutex_init返回值pthread_mutex_init()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EBUSY描述:该实现已检测到系统尝试重新初始化mp所引用的对象,即以前进行过初始化但尚未销毁的互斥锁。EINVAL描述:mattr属性值无效。互斥锁尚未修改。EFAULT描述:mp所指向的互斥锁的地址无效。使互斥保持一致如果某个互斥锁的属主失败,该互斥锁可能会变为不一致。使用pthread_mutex_consistent_np可使互斥对象mutex在其属主停止之后保持一致。pthread_mutex_consistent_np语法#includeintpthread_mutex_consistent_np(pthread_mutex_t*mutex);第4章•用同步对象编程93 使用互斥锁注–仅当定义了_POSIX_THREAD_PRIO_INHERIT符号时,pthread_mutex_consistent_np()才适用,并且仅适用于使用协议属性值PTHREAD_PRIO_INHERIT初始化的互斥锁。调用pthread_mutex_lock()会获取不一致的互斥锁。EOWNWERDEAD返回值表示出现不一致的互斥锁。持有以前通过调用pthread_mutex_lock()获取的互斥锁时可调用pthread_mutex_consistent_np()。如果互斥锁的属主失败,则该互斥锁保护的临界段可能会处于不一致状态。在这种情况下,仅当互斥锁保护的临界段可保持一致时,才能使该互斥锁保持一致。针对互斥锁调用pthread_mutex_lock()、pthread_mutex_unlock()和pthread_mutex_trylock()会以正常方式进行。对于不一致或者未持有的互斥锁,pthread_mutex_consistent_np()的行为是不确定的。pthread_mutex_consistent_np返回值pthread_mutex_consistent_np()在成功完成之后会返回零。其他任何返回值都表示出现了错误。pthread_mutex_consistent_np()会在出现以下情况时失败:ENOSYS描述:选项_POSIX_THREAD_PRIO_INHERIT未定义,或者该实现不支持pthread_mutex_consistent_np()。pthread_mutex_consistent_np()可能会在出现以下情况时失败:EINVAL描述:mattr属性值无效。锁定互斥锁使用pthread_mutex_lock(3C)可以锁定mutex所指向的互斥锁。pthread_mutex_lock语法intpthread_mutex_lock(pthread_mutex_t*mutex);#include94多线程编程指南•2006年10月 使用互斥锁pthread_mutex_tmutex;intret;ret=pthread_mutex_lock(&mp);/*acquirethemutex*/当pthread_mutex_lock()返回时,该互斥锁已被锁定。调用线程是该互斥锁的属主。如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止。对于Solaris线程,请参见第213页中的“mutex_lock语法”。如果互斥锁类型为PTHREAD_MUTEX_NORMAL,则不提供死锁检测。尝试重新锁定互斥锁会导致死锁。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或未锁定,则将产生不确定的行为。如果互斥锁类型为PTHREAD_MUTEX_ERRORCHECK,则会提供错误检查。如果某个线程尝试重新锁定的互斥锁已经由该线程锁定,则将返回错误。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。如果互斥锁类型为PTHREAD_MUTEX_RECURSIVE,则该互斥锁会保留锁定计数这一概念。线程首次成功获取互斥锁时,锁定计数会设置为1。线程每重新锁定该互斥锁一次,锁定计数就增加1。线程每解除锁定该互斥锁一次,锁定计数就减小1。锁定计数达到0时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。如果互斥锁类型是PTHREAD_MUTEX_DEFAULT,则尝试以递归方式锁定该互斥锁将产生不确定的行为。对于不是由调用线程锁定的互斥锁,如果尝试解除对它的锁定,则会产生不确定的行为。如果尝试解除锁定尚未锁定的互斥锁,则会产生不确定的行为。pthread_mutex_lock返回值pthread_mutex_lock()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EAGAIN描述:由于已超出了互斥锁递归锁定的最大次数,因此无法获取该互斥锁。EDEADLK描述:当前线程已经拥有互斥锁。如果定义了_POSIX_THREAD_PRIO_INHERIT符号,则会使用协议属性值PTHREAD_PRIO_INHERIT对互斥锁进行初始化。此外,如果pthread_mutexattr_setrobust_np()的robustness参数是PTHREAD_MUTEX_ROBUST_NP,则该函数将失败并返回以下值之一:第4章•用同步对象编程95 使用互斥锁EOWNERDEAD描述:该互斥锁的最后一个属主在持有该互斥锁时失败。该互斥锁现在由调用方拥有。调用方必须尝试使该互斥锁所保护的状态一致。如果调用方能够使状态保持一致,请针对该互斥锁调用pthread_mutex_consistent_np()并解除锁定该互斥锁。以后对pthread_mutex_lock()的调用都将正常进行。如果调用方无法使状态保持一致,请勿针对该互斥锁调用pthread_mutex_init(),但要解除锁定该互斥锁。以后调用pthread_mutex_lock()时将无法获取该互斥锁,并且将返回错误代码ENOTRECOVERABLE。如果获取该锁的属主失败并返回EOWNERDEAD,则下一个属主获取该锁时将返回EOWNERDEAD。ENOTRECOVERABLE描述:尝试获取的互斥锁正在保护某个状态,此状态由于该互斥锁以前的属主在持有该锁时失败而导致不可恢复。尚未获取该互斥锁。如果满足以下条件,则可能出现此不可恢复的情况:■以前获取该锁时返回EOWNERDEAD■该属主无法清除此状态■该属主已经解除锁定了该互斥锁,但是没有使互斥锁状态保持一致ENOMEM描述:已经超出了可同时持有的互斥锁数目的限制。解除锁定互斥锁使用pthread_mutex_unlock(3C)可以解除锁定mutex所指向的互斥锁。对于Solaris线程,请参见第214页中的“mutex_unlock语法”。pthread_mutex_unlock语法intpthread_mutex_unlock(pthread_mutex_t*mutex);#includepthread_mutex_tmutex;intret;ret=pthread_mutex_unlock(&mutex);/*releasethemutex*/96多线程编程指南•2006年10月 使用互斥锁pthread_mutex_unlock()可释放mutex引用的互斥锁对象。互斥锁的释放方式取决于互斥锁的类型属性。如果调用pthread_mutex_unlock()时有多个线程被mutex对象阻塞,则互斥锁变为可用时调度策略可确定获取该互斥锁的线程。对于PTHREAD_MUTEX_RECURSIVE类型的互斥锁,当计数达到零并且调用线程不再对该互斥锁进行任何锁定时,该互斥锁将变为可用。pthread_mutex_unlock返回值pthread_mutex_unlock()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EPERM描述:当前线程不拥有互斥锁。使用非阻塞互斥锁锁定使用pthread_mutex_trylock(3C)可以尝试锁定mutex所指向的互斥锁。对于Solaris线程,请参见第214页中的“mutex_trylock语法”。pthread_mutex_trylock语法intpthread_mutex_trylock(pthread_mutex_t*mutex);#includepthread_mutex_tmutex;intret;ret=pthread_mutex_trylock(&mutex);/*trytolockthemutex*/pthread_mutex_trylock()是pthread_mutex_lock()的非阻塞版本。如果mutex所引用的互斥对象当前被任何线程(包括当前线程)锁定,则将立即返回该调用。否则,该互斥锁将处于锁定状态,调用线程是其属主。pthread_mutex_trylock返回值pthread_mutex_trylock()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。第4章•用同步对象编程97 使用互斥锁EBUSY描述:由于mutex所指向的互斥锁已锁定,因此无法获取该互斥锁。EAGAIN描述:由于已超出了mutex的递归锁定最大次数,因此无法获取该互斥锁。如果定义了_POSIX_THREAD_PRIO_INHERIT符号,则会使用协议属性值PTHREAD_PRIO_INHERIT对互斥锁进行初始化。此外,如果pthread_mutexattr_setrobust_np()的robustness参数是PTHREAD_MUTEX_ROBUST_NP,则该函数将失败并返回以下值之一:EOWNERDEAD描述:该互斥锁的最后一个属主在持有该互斥锁时失败。该互斥锁现在由调用方拥有。调用方必须尝试使该互斥锁所保护的状态一致。如果调用方能够使状态保持一致,请针对该互斥锁调用pthread_mutex_consistent_np()并解除锁定该互斥锁。以后对pthread_mutex_lock()的调用都将正常进行。如果调用方无法使状态保持一致,请勿针对该互斥锁调用pthread_mutex_init(),而要解除锁定该互斥锁。以后调用pthread_mutex_trylock()时将无法获取该互斥锁,并且将返回错误代码ENOTRECOVERABLE。如果已获取该锁的属主失败并返回EOWNERDEAD,则下一个属主获取该锁时返回EOWNERDEAD。ENOTRECOVERABLE描述:尝试获取的互斥锁正在保护某个状态,此状态由于该互斥锁以前的属主在持有该锁时失败而导致不可恢复。尚未获取该互斥锁。以下条件下可能会出现此情况:■以前获取该锁时返回EOWNERDEAD■该属主无法清除此状态■该属主已经解除锁定了该互斥锁,但是没有使互斥锁状态保持一致ENOMEM描述:已经超出了可同时持有的互斥锁数目的限制。销毁互斥锁使用pthread_mutex_destroy(3C)可以销毁与mp所指向的互斥锁相关联的任何状态。对于Solaris线程,请参见第213页中的“mutex_destroy语法”。pthread_mutex_destroy语法intpthread_mutex_destroy(pthread_mutex_t*mp);98多线程编程指南•2006年10月 使用互斥锁#includepthread_mutex_tmp;intret;ret=pthread_mutex_destroy(&mp);/*mutexisdestroyed*/请注意,没有释放用来存储互斥锁的空间。pthread_mutex_destroy返回值pthread_mutex_destroy()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:mp指定的值不会引用已初始化的互斥锁对象。互斥锁定的代码示例示例4–1显示了使用互斥锁定的一些代码段。示例4–1互斥锁示例#includepthread_mutex_tcount_mutex;longlongcount;voidincrement_count(){pthread_mutex_lock(&count_mutex);第4章•用同步对象编程99 使用互斥锁示例4–1互斥锁示例(续)count=count+1;pthread_mutex_unlock(&count_mutex);}longlongget_count(){longlongc;pthread_mutex_lock(&count_mutex);c=count;pthread_mutex_unlock(&count_mutex);return(c);}示例4–1中的两个函数将互斥锁用于不同目的。increment_count()函数使用互斥锁确保对共享变量进行原子更新。get_count()函数使用互斥锁保证以原子方式读取64位数量count。在32位体系结构上,longlong实际上是两个32位数量。读取整数值时执行的是原子运算,因为整数是大多数计算机中常见的字长。锁分层结构的使用示例有时,可能需要同时访问两个资源。您可能正在使用其中的一个资源,随后发现还需要另一个资源。如果两个线程尝试声明这两个资源,但是以不同的顺序锁定与这些资源相关联的互斥锁,则会出现问题。例如,如果两个线程分别锁定互斥锁1和互斥锁2,则每个线程尝试锁定另一个互斥锁时,将会出现死锁。示例4–2说明了可能的死锁情况。100多线程编程指南•2006年10月 使用互斥锁示例4–2死锁线程1线程2pthread_mutex_lock(&m1);pthread_mutex_lock(&m2);/*useresource1*//*useresource2*/pthread_mutex_lock(&m2);pthread_mutex_lock(&m1);/*useresources1and2*//*useresources1and2*/pthread_mutex_unlock(&m2);pthread_mutex_unlock(&m1);pthread_mutex_unlock(&m1);pthread_mutex_unlock(&m2);避免此问题的最佳方法是,确保线程在锁定多个互斥锁时,以同样的顺序进行锁定。如果始终按照规定的顺序锁定,就不会出现死锁。此方法称为锁分层结构,它通过为互斥锁指定逻辑编号来对这些锁进行排序。另外,请注意以下限制:如果您持有的任何互斥锁其指定编号大于n,则不能提取指定编号为n的互斥锁。但是,不能始终使用此方法。有时,必须按照与规定不同的顺序提取互斥锁。要防止在这种情况下出现死锁,请使用pthread_mutex_trylock()。如果线程发现无法避免死锁时,该线程必须释放其互斥锁。示例4–3条件锁定线程1线程2pthread_mutex_lock(&m1);for(;;)pthread_mutex_lock(&m2);{pthread_mutex_lock(&m2);/*noprocessing*/if(pthread_mutex_trylock(&m1)==0)pthread_mutex_unlock(&m2);/*gotit*/pthread_mutex_unlock(&m1);break;/*didn'tgetit*/pthread_mutex_unlock(&m2);}/*getlocks;noprocessing*/pthread_mutex_unlock(&m1);pthread_mutex_unlock(&m2);第4章•用同步对象编程101 使用互斥锁在示例4–3中,线程1按照规定的顺序锁定互斥锁,但是线程2不按顺序提取互斥锁。要确保不会出现死锁,线程2必须非常小心地提取互斥锁1。如果线程2在等待该互斥锁释放时被阻塞,则线程2可能刚才已经与线程1进入了死锁状态。要确保线程2不会进入死锁状态,线程2需要调用pthread_mutex_trylock(),此函数可在该互斥锁可用时提取它。如果该互斥锁不可用,线程2将立即返回并报告提取失败。此时,线程2必须释放互斥锁2。线程1现在会锁定互斥锁2,然后释放互斥锁1和互斥锁2。嵌套锁定和单链接列表的结合使用示例示例4–4和示例4–5说明了如何同时提取三个锁。通过按照规定的顺序提取锁可避免出现死锁。示例4–4单链接列表结构typedefstructnode1{intvalue;structnode1*link;pthread_mutex_tlock;}node1_t;node1_tListHead;本示例针对每个包含一个互斥锁的节点使用单链接列表结构。要将某个节点从列表中删除,请首先从ListHead开始搜索列表,直到找到所需的节点为止。ListHead永远不会被删除。要防止执行此搜索时产生并发删除,请在访问每个节点的任何内容之前先锁定该节点。由于所有的搜索都从ListHead开始,并且始终按照列表中的顺序提取锁,因此不会出现死锁。因为更改涉及到两个节点,所以找到所需的节点之后,请锁定该节点及其前序节点。因为前序节点的锁总是最先提取,所以可再次防止出现死锁。示例4–5说明如何使用C代码来删除单链接列表中的项。示例4–5单链接列表和嵌套锁定node1_t*delete(intvalue){102多线程编程指南•2006年10月 使用互斥锁示例4–5单链接列表和嵌套锁定(续)node1_t*prev,*current;prev=&ListHead;pthread_mutex_lock(&prev->lock);while((current=prev->link)!=NULL){pthread_mutex_lock(¤t->lock);if(current->value==value){prev->link=current->link;pthread_mutex_unlock(¤t->lock);pthread_mutex_unlock(&prev->lock);current->link=NULL;return(current);}pthread_mutex_unlock(&prev->lock);prev=current;}pthread_mutex_unlock(&prev->lock);return(NULL);}嵌套锁定和循环链接列表的示例示例4–6通过将以前的列表结构转换为循环列表来对其进行修改。由于不再存在用于标识的头节点,因该线程可以与特定的节点相关联,并可针对该节点及其邻居执行操作。锁分层结构在此处不适用,因为链接之后的分层结构明显是循环结构。第4章•用同步对象编程103 使用互斥锁示例4–6循环链接列表结构typedefstructnode2{intvalue;structnode2*link;pthread_mutex_tlock;}node2_t;以下的C代码用来获取两个节点上的锁并执行涉及到这两个锁的操作。示例4–7循环链接列表和嵌套锁定voidHitNeighbor(node2_t*me){while(1){pthread_mutex_lock(&me->lock);if(pthread_mutex_lock(&me->link->lock)!=0){/*failedtogetlock*/pthread_mutex_unlock(&me->lock);continue;}break;}me->link->value+=me->value;me->value/=2;pthread_mutex_unlock(&me->link->lock);pthread_mutex_unlock(&me->lock);}104多线程编程指南•2006年10月 条件变量属性条件变量属性使用条件变量可以以原子方式阻塞线程,直到某个特定条件为真为止。条件变量始终与互斥锁一起使用。使用条件变量,线程可以以原子方式阻塞,直到满足某个条件为止。对条件的测试是在互斥锁(互斥)的保护下进行的。如果条件为假,线程通常会基于条件变量阻塞,并以原子方式释放等待条件变化的互斥锁。如果另一个线程更改了条件,该线程可能会向相关的条件变量发出信号,从而使一个或多个等待的线程执行以下操作:■唤醒■再次获取互斥锁■重新评估条件在以下情况下,条件变量可用于在进程之间同步线程:■线程是在可以写入的内存中分配的■内存由协作进程共享调度策略可确定唤醒阻塞线程的方式。对于缺省值SCHED_OTHER,将按优先级顺序唤醒线程。必须设置和初始化条件变量的属性,然后才能使用条件变量。表4–4列出了用于处理条件变量属性的函数。表4–4条件变量属性操作函数说明初始化条件变量属性第106页中的“pthread_condattr_init语法”删除条件变量属性第107页中的“pthread_condattr_destroy语法”设置条件变量的范围第107页中的“pthread_condattr_setpshared语法”获取条件变量的范围第108页中的“pthread_condattr_getpshared语法”表4–5中显示了定义条件变量的范围时Solaris线程和POSIX线程之间的差异。表4–5条件变量范围比较SolarisPOSIX定义USYNC_PROCESSPTHREAD_PROCESS_SHARED用于同步该进程和其他进程中的线程USYNC_THREADPTHREAD_PROCESS_PRIVATE用于仅同步该进程中的线程第4章•用同步对象编程105 条件变量属性初始化条件变量属性使用pthread_condattr_init(3C)可以将与该对象相关联的属性初始化为其缺省值。在执行过程中,线程系统会为每个属性对象分配存储空间。pthread_condattr_init语法intpthread_condattr_init(pthread_condattr_t*cattr);#includepthread_condattr_tcattr;intret;/*initializeanattributetodefaultvalue*/ret=pthread_condattr_init(&cattr);调用此函数时,pshared属性的缺省值为PTHREAD_PROCESS_PRIVATE。pshared的该值表示可以在进程内使用已初始化的条件变量。cattr的数据类型为opaque,其中包含一个由系统分配的属性对象。cattr范围可能的值为PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。PTHREAD_PROCESS_PRIVATE是缺省值。条件变量属性必须首先由pthread_condattr_destroy(3C)重新初始化后才能重用。pthread_condattr_init()调用会返回指向类型为opaque的对象的指针。如果未销毁该对象,则会导致内存泄漏。pthread_condattr_init返回值pthread_condattr_init()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。ENOMEM描述:分配的内存不足,无法初始化线程属性对象。EINVAL描述:cattr指定的值无效。删除条件变量属性使用pthread_condattr_destroy(3C)可以删除存储并使属性对象无效。106多线程编程指南•2006年10月 条件变量属性pthread_condattr_destroy语法intpthread_condattr_destroy(pthread_condattr_t*cattr);#includepthread_condattr_tcattr;intret;/*destroyanattribute*/ret=pthread_condattr_destroy(&cattr);pthread_condattr_destroy返回值pthread_condattr_destroy()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:cattr指定的值无效。设置条件变量的范围pthread_condattr_setpshared(3C)可用来将条件变量的范围设置为进程专用(进程内)或系统范围内(进程间)。pthread_condattr_setpshared语法intpthread_condattr_setpshared(pthread_condattr_t*cattr,intpshared);#includepthread_condattr_tcattr;intret;第4章•用同步对象编程107 条件变量属性/*allprocesses*/ret=pthread_condattr_setpshared(&cattr,PTHREAD_PROCESS_SHARED);/*withinaprocess*/ret=pthread_condattr_setpshared(&cattr,PTHREAD_PROCESS_PRIVATE);如果pshared属性在共享内存中设置为PTHREAD_PROCESS_SHARED,则其所创建的条件变量可以在多个进程中的线程之间共享。此行为与最初的Solaris线程实现中mutex_init()中的USYNC_PROCESS标志等效。如果互斥锁的pshared属性设置为PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建的线程才能够处理该互斥锁。PTHREAD_PROCESS_PRIVATE是缺省值。PTHREAD_PROCESS_PRIVATE所产生的行为与在最初的Solaris线程的cond_init()调用中使用USYNC_THREAD标志相同。PTHREAD_PROCESS_PRIVATE的行为与局部条件变量相同。PTHREAD_PROCESS_SHARED的行为与全局条件变量等效。pthread_condattr_setpshared返回值pthread_condattr_setpshared()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:cattr或pshared的值无效。获取条件变量的范围pthread_condattr_getpshared(3C)可用来获取属性对象cattr的pshared的当前值。pthread_condattr_getpshared语法intpthread_condattr_getpshared(constpthread_condattr_t*cattr,int*pshared);#includepthread_condattr_tcattr;intpshared;108多线程编程指南•2006年10月 使用条件变量intret;/*getpsharedvalueofconditionvariable*/ret=pthread_condattr_getpshared(&cattr,&pshared);属性对象的值为PTHREAD_PROCESS_SHARED或PTHREAD_PROCESS_PRIVATE。pthread_condattr_getpshared返回值pthread_condattr_getpshared()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:cattr的值无效。使用条件变量本节介绍如何使用条件变量。表4–6列出了可用的函数。表4–6条件变量函数操作相关函数说明初始化条件变量第110页中的“pthread_cond_init语法”基于条件变量阻塞第111页中的“pthread_cond_wait语法”解除阻塞特定线程第112页中的“pthread_cond_signal语法”在指定的时间之前阻塞第114页中的“pthread_cond_timedwait语法”在指定的时间间隔内阻塞第116页中的“pthread_cond_reltimedwait_np语法”解除阻塞所有线程第117页中的“pthread_cond_broadcast语法”销毁条件变量状态第119页中的“pthread_cond_destroy语法”初始化条件变量使用pthread_cond_init(3C)可以将cv所指示的条件变量初始化为其缺省值,或者指定已经使用pthread_condattr_init()设置的条件变量属性。第4章•用同步对象编程109 使用条件变量pthread_cond_init语法intpthread_cond_init(pthread_cond_t*cv,constpthread_condattr_t*cattr);#includepthread_cond_tcv;pthread_condattr_tcattr;intret;/*initializeaconditionvariabletoitsdefaultvalue*/ret=pthread_cond_init(&cv,NULL);/*initializeaconditionvariable*/ret=pthread_cond_init(&cv,&cattr);cattr设置为NULL。将cattr设置为NULL与传递缺省条件变量属性对象的地址等效,但是没有内存开销。对于Solaris线程,请参见第215页中的“cond_init语法”。使用PTHREAD_COND_INITIALIZER宏可以将以静态方式定义的条件变量初始化为其缺省属性。PTHREAD_COND_INITIALIZER宏与动态分配具有null属性的pthread_cond_init()等效,但是不进行错误检查。多个线程决不能同时初始化或重新初始化同一个条件变量。如果要重新初始化或销毁某个条件变量,则应用程序必须确保该条件变量未被使用。pthread_cond_init返回值pthread_cond_init()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:cattr指定的值无效。EBUSY描述:条件变量处于使用状态。110多线程编程指南•2006年10月 使用条件变量EAGAIN描述:必要的资源不可用。ENOMEM描述:内存不足,无法初始化条件变量。基于条件变量阻塞使用pthread_cond_wait(3C)可以以原子方式释放mp所指向的互斥锁,并导致调用线程基于cv所指向的条件变量阻塞。对于Solaris线程,请参见第217页中的“cond_wait语法”。pthread_cond_wait语法intpthread_cond_wait(pthread_cond_t*cv,pthread_mutex_t*mutex);#includepthread_cond_tcv;pthread_mutex_tmp;intret;/*waitonconditionvariable*/ret=pthread_cond_wait(&cv,&mp);阻塞的线程可以通过pthread_cond_signal()或pthread_cond_broadcast()唤醒,也可以在信号传送将其中断时唤醒。不能通过pthread_cond_wait()的返回值来推断与条件变量相关联的条件的值的任何变化。必须重新评估此类条件。pthread_cond_wait()例程每次返回结果时调用线程都会锁定并且拥有互斥锁,即使返回错误时也是如此。该条件获得信号之前,该函数一直被阻塞。该函数会在被阻塞之前以原子方式释放相关的互斥锁,并在返回之前以原子方式再次获取该互斥锁。通常,对条件表达式的评估是在互斥锁的保护下进行的。如果条件表达式为假,线程会基于条件变量阻塞。然后,当该线程更改条件值时,另一个线程会针对条件变量发出信号。这种变化会导致所有等待该条件的线程解除阻塞并尝试再次获取互斥锁。第4章•用同步对象编程111 使用条件变量必须重新测试导致等待的条件,然后才能从pthread_cond_wait()处继续执行。唤醒的线程重新获取互斥锁并从pthread_cond_wait()返回之前,条件可能会发生变化。等待线程可能并未真正唤醒。建议使用的测试方法是,将条件检查编写为调用pthread_cond_wait()的while()循环。pthread_mutex_lock();while(condition_is_false)pthread_cond_wait();pthread_mutex_unlock();如果有多个线程基于该条件变量阻塞,则无法保证按特定的顺序获取互斥锁。注–pthread_cond_wait()是取消点。如果取消处于暂挂状态,并且调用线程启用了取消功能,则该线程会终止,并在继续持有该锁的情况下开始执行清除处理程序。pthread_cond_wait返回值pthread_cond_wait()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:cv或mp指定的值无效。解除阻塞一个线程对于基于cv所指向的条件变量阻塞的线程,使用pthread_cond_signal(3C)可以解除阻塞该线程。对于Solaris线程,请参见第219页中的“cond_signal语法”。pthread_cond_signal语法intpthread_cond_signal(pthread_cond_t*cv);#includepthread_cond_tcv;intret;112多线程编程指南•2006年10月 使用条件变量/*oneconditionvariableissignaled*/ret=pthread_cond_signal(&cv);应在互斥锁的保护下修改相关条件,该互斥锁用于获得信号的条件变量中。否则,可能在条件变量的测试和pthread_cond_wait()阻塞之间修改该变量,这会导致无限期等待。调度策略可确定唤醒阻塞线程的顺序。对于SCHED_OTHER,将按优先级顺序唤醒线程。如果没有任何线程基于条件变量阻塞,则调用pthread_cond_signal()不起作用。示例4–8使用pthread_cond_wait()和pthread_cond_signal()pthread_mutex_tcount_lock;pthread_cond_tcount_nonzero;unsignedcount;decrement_count(){pthread_mutex_lock(&count_lock);while(count==0)pthread_cond_wait(&count_nonzero,&count_lock);count=count-1;pthread_mutex_unlock(&count_lock);}increment_count(){pthread_mutex_lock(&count_lock);if(count==0)第4章•用同步对象编程113 使用条件变量示例4–8使用pthread_cond_wait()和pthread_cond_signal()(续)pthread_cond_signal(&count_nonzero);count=count+1;pthread_mutex_unlock(&count_lock);}pthread_cond_signal返回值pthread_cond_signal()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:cv指向的地址非法。示例4–8说明了如何使用pthread_cond_wait()和pthread_cond_signal()。在指定的时间之前阻塞pthread_cond_timedwait(3C)的用法与pthread_cond_wait()的用法基本相同,区别在于在由abstime指定的时间之后pthread_cond_timedwait()不再被阻塞。pthread_cond_timedwait语法intpthread_cond_timedwait(pthread_cond_t*cv,pthread_mutex_t*mp,conststructtimespec*abstime);#include#includepthread_cond_tcv;pthread_mutex_tmp;timestruct_tabstime;intret;114多线程编程指南•2006年10月 使用条件变量/*waitonconditionvariable*/ret=pthread_cond_timedwait(&cv,&mp,&abstime);pthread_cond_timewait()每次返回时调用线程都会锁定并且拥有互斥锁,即使pthread_cond_timedwait()返回错误时也是如此。对于Solaris线程,请参见第218页中的“cond_timedwait语法”。pthread_cond_timedwait()函数会一直阻塞,直到该条件获得信号,或者最后一个参数所指定的时间已过为止。注–pthread_cond_timedwait()也是取消点。示例4–9计时条件等待pthread_timestruc_tto;pthread_mutex_tm;pthread_cond_tc;...pthread_mutex_lock(&m);to.tv_sec=time(NULL)+TIMEOUT;to.tv_nsec=0;while(cond==FALSE){err=pthread_cond_timedwait(&c,&m,&to);if(err==ETIMEDOUT){/*timeout,dosomething*/break;}}pthread_mutex_unlock(&m);第4章•用同步对象编程115 使用条件变量pthread_cond_timedwait返回值pthread_cond_timedwait()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:cv或abstime指向的地址非法。ETIMEDOUT描述:abstime指定的时间已过。超时会指定为当天时间,以便在不重新计算值的情况下高效地重新测试条件,如示例4–9中所示。在指定的时间间隔内阻塞pthread_cond_reltimedwait_np(3C)的用法与pthread_cond_timedwait()的用法基本相同,唯一的区别在于pthread_cond_reltimedwait_np()会采用相对时间间隔而不是将来的绝对时间作为其最后一个参数的值。pthread_cond_reltimedwait_np语法intpthread_cond_reltimedwait_np(pthread_cond_t*cv,pthread_mutex_t*mp,conststructtimespec*reltime);#include#includepthread_cond_tcv;pthread_mutex_tmp;timestruct_treltime;intret;/*waitonconditionvariable*/ret=pthread_cond_reltimedwait_np(&cv,&mp,&reltime);116多线程编程指南•2006年10月 使用条件变量pthread_cond_reltimedwait_np()每次返回时调用线程都会锁定并且拥有互斥锁,即使pthread_cond_reltimedwait_np()返回错误时也是如此。对于Solaris线程,请参见cond_reltimedwait(3C)。pthread_cond_reltimedwait_np()函数会一直阻塞,直到该条件获得信号,或者最后一个参数指定的时间间隔已过为止。注–pthread_cond_reltimedwait_np()也是取消点。pthread_cond_reltimedwait_np返回值pthread_cond_reltimedwait_np()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:cv或reltime指示的地址非法。ETIMEDOUT描述:reltime指定的时间间隔已过。解除阻塞所有线程对于基于cv所指向的条件变量阻塞的线程,使用pthread_cond_broadcast(3C)可以解除阻塞所有这些线程,这由pthread_cond_wait()来指定。pthread_cond_broadcast语法intpthread_cond_broadcast(pthread_cond_t*cv);#includepthread_cond_tcv;intret;/*allconditionvariablesaresignaled*/ret=pthread_cond_broadcast(&cv);如果没有任何线程基于该条件变量阻塞,则调用pthread_cond_broadcast()不起作用。对于Solaris线程,请参见第220页中的“cond_broadcast语法”。第4章•用同步对象编程117 使用条件变量由于pthread_cond_broadcast()会导致所有基于该条件阻塞的线程再次争用互斥锁,因此请谨慎使用pthread_cond_broadcast()。例如,通过使用pthread_cond_broadcast(),线程可在资源释放后争用不同的资源量,如示例4–10中所示。示例4–10条件变量广播pthread_mutex_trsrc_lock;pthread_cond_trsrc_add;unsignedintresources;get_resources(intamount){pthread_mutex_lock(&rsrc_lock);while(resourcespthread_cond_tcv;intret;/*Conditionvariableisdestroyed*/ret=pthread_cond_destroy(&cv);请注意,没有释放用来存储条件变量的空间。pthread_cond_destroy返回值pthread_cond_destroy()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:cv指定的值无效。第4章•用同步对象编程119 使用条件变量唤醒丢失问题如果线程未持有与条件相关联的互斥锁,则调用pthread_cond_signal()或pthread_cond_broadcast()会产生唤醒丢失错误。满足以下所有条件时,即会出现唤醒丢失问题:■一个线程调用pthread_cond_signal()或pthread_cond_broadcast()■另一个线程已经测试了该条件,但是尚未调用pthread_cond_wait()■没有正在等待的线程信号不起作用,因此将会丢失仅当修改所测试的条件但未持有与之相关联的互斥锁时,才会出现此问题。只要仅在持有关联的互斥锁同时修改所测试的条件,即可调用pthread_cond_signal()和pthread_cond_broadcast(),而无论这些函数是否持有关联的互斥锁。生成方和使用者问题并发编程中收集了许多标准的众所周知的问题,生成方和使用者问题只是其中的一个问题。此问题涉及到一个大小限定的缓冲区和两类线程(生成方和使用者),生成方将项放入缓冲区中,然后使用者从缓冲区中取走项。生成方必须在缓冲区中有可用空间之后才能向其中放置内容。使用者必须在生成方向缓冲区中写入之后才能从中提取内容。条件变量表示一个等待某个条件获得信号的线程队列。示例4–11中包含两个此类队列。一个队列(less)针对生成方,用于等待缓冲区中出现空位置。另一个队列(more)针对使用者,用于等待从缓冲槽位的空位置中提取其中包含的信息。该示例中还包含一个互斥锁,因为描述该缓冲区的数据结构一次只能由一个线程访问。示例4–11生成方和使用者的条件变量问题typedefstruct{charbuf[BSIZE];intoccupied;intnextin;intnextout;pthread_mutex_tmutex;120多线程编程指南•2006年10月 使用条件变量示例4–11生成方和使用者的条件变量问题(续)pthread_cond_tmore;pthread_cond_tless;}buffer_t;buffer_tbuffer;如示例4–12中所示,生成方线程获取该互斥锁以保护buffer数据结构,然后,缓冲区确定是否有空间可用于存放所生成的项。如果没有可用空间,生成方线程会调用pthread_cond_wait()。pthread_cond_wait()会导致生成方线程连接正在等待less条件获得信号的线程队列。less表示缓冲区中的可用空间。与此同时,在调用pthread_cond_wait()的过程中,该线程会释放互斥锁的锁定。正在等待的生成方线程依赖于使用者线程在条件为真时发出信号,如示例4–12中所示。该条件获得信号时,将会唤醒等待less的第一个线程。但是,该线程必须再次锁定互斥锁,然后才能从pthread_cond_wait()返回。获取互斥锁可确保该线程再次以独占方式访问缓冲区的数据结构。该线程随后必须检查缓冲区中是否确实存在可用空间。如果空间可用,该线程会向下一个可用的空位置中进行写入。与此同时,使用者线程可能正在等待项出现在缓冲区中。这些线程正在等待条件变量more。刚在缓冲区中存储内容的生成方线程会调用pthread_cond_signal()以唤醒下一个正在等待的使用者。如果没有正在等待的使用者,此调用将不起作用。最后,生成方线程会解除锁定互斥锁,从而允许其他线程处理缓冲区的数据结构。示例4–12生成方和使用者问题:生成方voidproducer(buffer_t*b,charitem){pthread_mutex_lock(&b->mutex);while(b->occupied>=BSIZE)pthread_cond_wait(&b->less,&b->mutex);第4章•用同步对象编程121 使用条件变量示例4–12生成方和使用者问题:生成方(续)assert(b->occupiedbuf[b->nextin++]=item;b->nextin%=BSIZE;b->occupied++;/*now:eitherb->occupiednextinistheindexofthenextemptyslotinthebuffer,orb->occupied==BSIZEandb->nextinistheindexofthenext(occupied)slotthatwillbeemptiedbyaconsumer(suchasb->nextin==b->nextout)*/pthread_cond_signal(&b->more);pthread_mutex_unlock(&b->mutex);}请注意assert()语句的用法。除非在编译代码时定义了NDEBUG,否则assert()在其参数的计算结果为真(非零)时将不执行任何操作。如果参数的计算结果为假(零),则该程序会中止。在多线程程序中,此类断言特别有用。如果断言失败,assert()会立即指出运行时问题。assert()还有另一个作用,即提供有用的注释。以/*now:eitherb->occupied...开头的注释最好以断言形式表示,但是由于语句过于复杂,无法用布尔值表达式来表示,因此将用英语表示。122多线程编程指南•2006年10月 使用条件变量断言和注释都是不变量的示例。这些不变量是逻辑语句,在程序正常执行时不应将其声明为假,除非是线程正在修改不变量中提到的一些程序变量时的短暂修改过程中。当然,只要有线程执行语句,断言就应当为真。使用不变量是一种极为有用的方法。即使没有在程序文本中声明不变量,在分析程序时也应将其视为不变量。每次线程执行包含注释的代码时,生成方代码中表示为注释的不变量始终为真。如果将此注释移到紧挨mutex_unlock()的后面,则注释不一定仍然为真。如果将此注释移到紧跟assert()之后的位置,则注释仍然为真。因此,不变量可用于表示一个始终为真的属性,除非一个生成方或一个使用者正在更改缓冲区的状态。线程在互斥锁的保护下处理缓冲区时,该线程可能会暂时声明不变量为假。但是,一旦线程结束对缓冲区的操作,不变量即会恢复为真。示例4–13给出了使用者的代码。该逻辑流程与生成方的逻辑流程相对称。示例4–13生成方和使用者问题:使用者charconsumer(buffer_t*b){charitem;pthread_mutex_lock(&b->mutex);while(b->occupied<=0)pthread_cond_wait(&b->more,&b->mutex);assert(b->occupied>0);item=b->buf[b->nextout++];b->nextout%=BSIZE;b->occupied--;/*now:eitherb->occupied>0andb->nextoutistheindexofthenextoccupiedslotinthebuffer,or第4章•用同步对象编程123 使用信号进行同步示例4–13生成方和使用者问题:使用者(续)b->occupied==0andb->nextoutistheindexofthenext(empty)slotthatwillbefilledbyaproducer(suchasb->nextout==b->nextin)*/pthread_cond_signal(&b->less);pthread_mutex_unlock(&b->mutex);return(item);}使用信号进行同步信号是E.W.Dijkstra在二十世纪六十年代末设计的一种编程架构。Dijkstra的模型与铁路操作有关:假设某段铁路是单线的,因此一次只允许一列火车通过。信号将用于同步通过该轨道的火车。火车在进入单一轨道之前必须等待信号灯变为允许通行的状态。火车进入轨道后,会改变信号状态,防止其他火车进入该轨道。火车离开这段轨道时,必须再次更改信号的状态,以便允许其他火车进入轨道。在计算机版本中,信号以简单整数来表示。线程等待获得许可以便继续运行,然后发出信号,表示该线程已经通过针对信号执行P操作来继续运行。线程必须等到信号的值为正,然后才能通过将信号值减1来更改该值。完成此操作后,线程会执行V操作,即通过将信号值加1来更改该值。这些操作必须以原子方式执行,不能再将其划分成子操作,即,在这些子操作之间不能对信号执行其他操作。在P操作中,信号值在减小之前必须为正,从而确保生成的信号值不为负,并且比该值减小之前小1。在P和V操作中,必须在没有干扰的情况下进行运算。如果针对同一信号同时执行两个V操作,则实际结果是信号的新值比原来大2。对于大多数人来说,如同记住Dijkstra是荷兰人一样,记住P和V本身的含义并不重要。但是,从真正学术的角度来说,P代表prolagen,这是由proberenteverlagen演变而来的杜撰词,其意思是尝试减小。V代表verhogen,其意思是增加。Dijkstra的技术说明EWD74中介绍了这些含义。124多线程编程指南•2006年10月 使用信号进行同步sem_wait(3RT)和sem_post(3RT)分别与Dijkstra的P和V操作相对应。sem_trywait(3RT)是P操作的一种条件形式。如果调用线程不等待就不能减小信号的值,则该调用会立即返回一个非零值。有两种基本信号:二进制信号和计数信号量。二进制信号的值只能是0或1,计数信号量可以是任意非负值。二进制信号在逻辑上相当于一个互斥锁。不过,尽管不会强制,但互斥锁应当仅由持有该锁的线程来解除锁定。因为不存在“持有信号的线程”这一概念,所以,任何线程都可以执行V或sem_post(3RT)操作。计数信号量与互斥锁一起使用时的功能几乎与条件变量一样强大。在许多情况下,使用计数信号量实现的代码比使用条件变量实现的代码更为简单,如示例4–14、示例4–15和示例4–16中所示。但是,将互斥锁用于条件变量时,会存在一个隐含的括号。该括号可以清楚表明程序受保护的部分。对于信号则不必如此,可以使用并发编程当中的goto对其进行调用。信号的功能强大,但是容易以非结构化的不确定方式使用。命名信号和未命名信号POSIX信号可以是未命名的,也可以是命名的。未命名信号在进程内存中分配,并会进行初始化。未命名信号可能可供多个进程使用,具体取决于信号的分配和初始化的方式。未命名信号可以是通过fork()继承的专用信号,也可以通过用来分配和映射这些信号的常规文件的访问保护功能对其进行保护。命名信号类似于进程共享的信号,区别在于命名信号是使用路径名而非pshared值引用的。命名信号可以由多个进程共享。命名信号具有属主用户ID、组ID和保护模式。对于open、retrieve、close和remove命名信号,可以使用以下函数:sem_open、sem_getvalue、sem_close和sem_unlink。通过使用sem_open,可以创建一个命名信号,其名称是在文件系统的名称空间中定义的。有关命名信号的更多信息,请参见sem_open、sem_getvalue、sem_close和sem_unlink手册页。计数信号量概述从概念上来说,信号量是一个非负整数计数。信号量通常用来协调对资源的访问,其中信号计数会初始化为可用资源的数目。然后,线程在资源增加时会增加计数,在删除资源时会减小计数,这些操作都以原子方式执行。如果信号计数变为零,则表明已无可用资源。计数为零时,尝试减小信号的线程会被阻塞,直到计数大于零为止。第4章•用同步对象编程125 使用信号进行同步表4–7信号例程操作相关函数说明初始化信号第126页中的“sem_init语法”增加信号第128页中的“sem_post语法”基于信号计数阻塞第129页中的“sem_wait语法”减小信号计数第130页中的“sem_trywait语法”销毁信号状态第130页中的“sem_destroy语法”由于信号无需由同一个线程来获取和释放,因此信号可用于异步事件通知,如用于信号处理程序中。同时,由于信号包含状态,因此可以异步方式使用,而不用象条件变量那样要求获取互斥锁。但是,信号的效率不如互斥锁高。缺省情况下,如果有多个线程正在等待信号,则解除阻塞的顺序是不确定的。信号在使用前必须先初始化,但是信号没有属性。初始化信号使用sem_init(3RT)可以将sem所指示的未命名信号变量初始化为value。sem_init语法intsem_init(sem_t*sem,intpshared,unsignedintvalue);#includesem_tsem;intpshared;intret;intvalue;/*initializeaprivatesemaphore*/pshared=0;126多线程编程指南•2006年10月 使用信号进行同步value=1;ret=sem_init(&sem,pshared,value);如果pshared的值为零,则不能在进程之间共享信号。如果pshared的值不为零,则可以在进程之间共享信号。对于Solaris线程,请参见第220页中的“sema_init语法”。多个线程决不能初始化同一个信号。不得对其他线程正在使用的信号重新初始化。初始化进程内信号pshared为0时,信号只能由该进程内的所有线程使用。#includesem_tsem;intret;intcount=4;/*tobeusedwithinthisprocessonly*/ret=sem_init(&sem,0,count);初始化进程间信号pshared不为零时,信号可以由其他进程共享。#includesem_tsem;intret;intcount=4;/*tobesharedamongprocesses*/第4章•用同步对象编程127 使用信号进行同步ret=sem_init(&sem,1,count);sem_init返回值sem_init()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:参数值超过了SEM_VALUE_MAX。ENOSPC描述:初始化信号所需的资源已经用完。到达信号的SEM_NSEMS_MAX限制。ENOSYS描述:系统不支持sem_init()函数。EPERM描述:进程缺少初始化信号所需的适当权限。增加信号使用sem_post(3RT)可以原子方式增加sem所指示的信号。sem_post语法intsem_post(sem_t*sem);#includesem_tsem;intret;ret=sem_post(&sem);/*semaphoreisposted*/如果所有线程均基于信号阻塞,则会对其中一个线程解除阻塞。对于Solaris线程,请参见第222页中的“sema_post语法”。128多线程编程指南•2006年10月 使用信号进行同步sem_post返回值sem_post()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:sem所指示的地址非法。基于信号计数进行阻塞使用sem_wait(3RT)可以阻塞调用线程,直到sem所指示的信号计数大于零为止,之后以原子方式减小计数。sem_wait语法intsem_wait(sem_t*sem);#includesem_tsem;intret;ret=sem_wait(&sem);/*waitforsemaphore*/sem_wait返回值sem_wait()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:sem所指示的地址非法。EINTR描述:此函数已被信号中断。减小信号计数使用sem_trywait(3RT)可以在计数大于零时,尝试以原子方式减小sem所指示的信号计数。第4章•用同步对象编程129 使用信号进行同步sem_trywait语法intsem_trywait(sem_t*sem);#includesem_tsem;intret;ret=sem_trywait(&sem);/*trytowaitforsemaphore*/此函数是sem_wait()的非阻塞版本。sem_trywait()在失败时会立即返回。sem_trywait返回值sem_trywait()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:sem所指示的地址非法。EINTR描述:此函数已被信号中断。EAGAIN描述:信号已为锁定状态,因此该信号不能通过sem_trywait()操作立即锁定。销毁信号状态使用sem_destroy(3RT)可以销毁与sem所指示的未命名信号相关联的任何状态。sem_destroy语法intsem_destroy(sem_t*sem);#includesem_tsem;130多线程编程指南•2006年10月 使用信号进行同步intret;ret=sem_destroy(&sem);/*thesemaphoreisdestroyed*/不会释放用来存储信号的空间。对于Solaris线程,请参见第224页中的“sema_destroy(3C)语法”。sem_destroy返回值sem_destroy()在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL描述:sem所指示的地址非法。使用信号时的生成方和使用者问题示例4–14中的数据结构与示例4–11中所示的用于条件变量示例的结构类似。两个信号分别表示空缓冲区和满缓冲区的数目,通过这些信号可确保生成方等待缓冲区变空,使用者等待缓冲区变满为止。示例4–14使用信号时的生成方和使用者问题typedefstruct{charbuf[BSIZE];sem_toccupied;sem_tempty;intnextin;intnextout;sem_tpmut;sem_tcmut;}buffer_t;buffer_tbuffer;第4章•用同步对象编程131 使用信号进行同步示例4–14使用信号时的生成方和使用者问题(续)sem_init(&buffer.occupied,0,0);sem_init(&buffer.empty,0,BSIZE);sem_init(&buffer.pmut,0,1);sem_init(&buffer.cmut,0,1);buffer.nextin=buffer.nextout=0;另一对二进制信号与互斥锁作用相同。在多个生成方使用多个空缓冲槽位,以及多个使用者使用多个满缓冲槽位的情况下,信号可用来控制对缓冲区的访问。在这种情况下,使用互斥锁可能会更好,但这里主要是为了演示信号的用法。示例4–15生成方和使用者问题:生成方voidproducer(buffer_t*b,charitem){sem_wait(&b->empty);sem_wait(&b->pmut);b->buf[b->nextin]=item;b->nextin++;b->nextin%=BSIZE;sem_post(&b->pmut);sem_post(&b->occupied);}示例4–16生成方和使用者问题:使用者charconsumer(buffer_t*b){132多线程编程指南•2006年10月 读写锁属性示例4–16生成方和使用者问题:使用者(续)charitem;sem_wait(&b->occupied);sem_wait(&b->cmut);item=b->buf[b->nextout];b->nextout++;b->nextout%=BSIZE;sem_post(&b->cmut);sem_post(&b->empty);return(item);}读写锁属性通过读写锁,可以对受保护的共享资源进行并发读取和独占写入。读写锁是可以在读取或写入模式下锁定的单一实体。要修改资源,线程必须首先获取互斥写锁。必须释放所有读锁之后,才允许使用互斥写锁。有关Solaris线程所实现的读写锁,请参见第192页中的“相似的同步函数-读写锁”。对数据库的访问可以使用读写锁进行同步。读写锁支持并发读取数据库记录,因为读操作不会更改记录的信息。要更新数据库时,写操作必须获取互斥写锁。第4章•用同步对象编程133 读写锁属性要更改缺省的读写锁属性,可以声明和初始化属性对象。通常,可以在应用程序开头的某个位置设置读写锁属性,设置在应用程序的起始位置可使属性更易于查找和修改。下表列出了本节中讨论的用来处理读写锁属性的函数。表4–8读写锁属性例程操作相关函数说明初始化读写锁属性第134页中的“pthread_rwlockattr_init语法”销毁读写锁属性第135页中的“pthread_rwlockattr_destroy语法”设置读写锁属性第135页中的“pthread_rwlockattr_setpshared语法”获取读写锁属性第136页中的“pthread_rwlockattr_getpshared语法”初始化读写锁属性pthread_rwlockattr_init(3C)使用实现中定义的所有属性的缺省值来初始化读写锁属性对象attr。pthread_rwlockattr_init语法#includeintpthread_rwlockattr_init(pthread_rwlockattr_t*attr);如果调用pthread_rwlockattr_init来指定已初始化的读写锁属性对象,则结果是不确定的。读写锁属性对象初始化一个或多个读写锁之后,影响该对象的任何函数(包括销毁)不会影响先前已初始化的读写锁。pthread_rwlockattr_init返回值如果成功,pthread_rwlockattr_init()会返回零。否则,将返回用于指明错误的错误号。ENOMEM描述:内存不足,无法初始化读写锁属性对象。销毁读写锁属性pthread_rwlockattr_destroy(3C)可用来销毁读写锁属性对象。134多线程编程指南•2006年10月 读写锁属性pthread_rwlockattr_destroy语法#includeintpthread_rwlockattr_destroy(pthread_rwlockattr_t*attr);在再次调用pthread_rwlockattr_init()重新初始化该对象之前,使用该对象所产生的影响是不确定的。实现可以导致pthread_rwlockattr_destroy()将attr所引用的对象设置为无效值。pthread_rwlockattr_destroy返回值如果成功,pthread_rwlockattr_destroy()会返回零。否则,将返回用于指明错误的错误号。EINVAL描述:attr指定的值无效。设置读写锁属性pthread_rwlockattr_setpshared(3C)可用来设置由进程共享的读写锁属性。pthread_rwlockattr_setpshared语法#includeintpthread_rwlockattr_setpshared(pthread_rwlockattr_t*attr,intpshared);读写锁属性可以为以下值之一:PTHREAD_PROCESS_SHARED描述:允许可访问用于分配读写锁的内存的任何线程对读写锁进行处理。即使该锁是在由多个进程共享的内存中分配的,也允许对其进行处理。PTHREAD_PROCESS_PRIVATE描述:读写锁只能由某些线程处理,这些线程与初始化该锁的线程在同一进程中创建。如果不同进程的线程尝试对此类读写锁进行处理,则其行为是不确定的。由进程共享的属性的缺省值为PTHREAD_PROCESS_PRIVATE。第4章•用同步对象编程135 使用读写锁pthread_rwlockattr_setpshared返回值如果成功,pthread_rwlockattr_setpshared()会返回零。否则,将返回用于指明错误的错误号。EINVAL描述:attr或pshared指定的值无效。获取读写锁属性pthread_rwlockattr_getpshared(3C)可用来获取由进程共享的读写锁属性。pthread_rwlockattr_getpshared语法#includeintpthread_rwlockattr_getpshared(constpthread_rwlockattr_t*attr,int*pshared);pthread_rwlockattr_getpshared()从attr引用的已初始化属性对象中获取由进程共享的属性的值。pthread_rwlockattr_getpshared返回值如果成功,pthread_rwlockattr_getpshared()会返回零。否则,将返回用于指明错误的错误号。EINVAL描述:attr或pshared指定的值无效。使用读写锁配置读写锁的属性之后,即可初始化读写锁。以下函数用于初始化或销毁读写锁、锁定或解除锁定读写锁或尝试锁定读写锁。下表列出了本节中讨论的用来处理读写锁的函数。136多线程编程指南•2006年10月 使用读写锁表4–9处理读写锁的例程操作相关函数说明初始化读写锁第137页中的“pthread_rwlock_init语法”读取读写锁中的锁第138页中的“pthread_rwlock_rdlock语法”读取非阻塞读写锁中的锁第139页中的“pthread_rwlock_tryrdlock语法”写入读写锁中的锁第139页中的“pthread_rwlock_wrlock语法”写入非阻塞读写锁中的锁第140页中的“pthread_rwlock_trywrlock语法”解除锁定读写锁第140页中的“pthread_rwlock_unlock语法”销毁读写锁第141页中的“pthread_rwlock_destroy语法”初始化读写锁使用pthread_rwlock_init(3C)可以通过attr所引用的属性初始化rwlock所引用的读写锁。pthread_rwlock_init语法#includeintpthread_rwlock_init(pthread_rwlock_t*rwlock,constpthread_rwlockattr_t*attr);pthread_rwlock_trwlock=PTHREAD_RWLOCK_INITIALIZER;如果attr为NULL,则使用缺省的读写锁属性,其作用与传递缺省读写锁属性对象的地址相同。初始化读写锁之后,该锁可以使用任意次数,而无需重新初始化。成功初始化之后,读写锁的状态会变为已初始化和未锁定。如果调用pthread_rwlock_init()来指定已初始化的读写锁,则结果是不确定的。如果读写锁在使用之前未初始化,则结果是不确定的。对于Solaris线程,请参见第193页中的“rwlock_init语法”。如果缺省的读写锁属性适用,则PTHREAD_RWLOCK_INITIALIZER宏可初始化以静态方式分配的读写锁,其作用与通过调用pthread_rwlock_init()并将参数attr指定为NULL进行动态初始化等效,区别在于不会执行错误检查。pthread_rwlock_init返回值如果成功,pthread_rwlock_init()会返回零。否则,将返回用于指明错误的错误号。第4章•用同步对象编程137 使用读写锁如果pthread_rwlock_init()失败,将不会初始化rwlock,并且rwlock的内容是不确定的。EINVAL描述:attr或rwlock指定的值无效。获取读写锁中的读锁pthread_rwlock_rdlock(3C)可用来向rwlock所引用的读写锁应用读锁。pthread_rwlock_rdlock语法#includeintpthread_rwlock_rdlock(pthread_rwlock_t*rwlock);如果写入器未持有读锁,并且没有任何写入器基于该锁阻塞,则调用线程会获取读锁。如果写入器未持有读锁,但有多个写入器正在等待该锁时,调用线程是否能获取该锁是不确定的。如果某个写入器持有读锁,则调用线程无法获取该锁。如果调用线程未获取读锁,则它将阻塞。调用线程必须获取该锁之后,才能从pthread_rwlock_rdlock()返回。如果在进行调用时,调用线程持有rwlock中的写锁,则结果是不确定的。为避免写入器资源匮乏,允许在多个实现中使写入器的优先级高于读取器。例如,Solaris线程实现中写入器的优先级高于读取器。请参见第194页中的“rw_rdlock语法”。一个线程可以在rwlock中持有多个并发的读锁,该线程可以成功调用pthread_rwlock_rdlock()n次。该线程必须调用pthread_rwlock_unlock()n次才能执行匹配的解除锁定操作。如果针对未初始化的读写锁调用pthread_rwlock_rdlock(),则结果是不确定的。线程信号处理程序可以处理传送给等待读写锁的线程的信号。从信号处理程序返回后,线程将继续等待读写锁以执行读取,就好像线程未中断一样。pthread_rwlock_rdlock返回值如果成功,pthread_rwlock_rdlock()会返回零。否则,将返回用于指明错误的错误号。EINVAL描述:attr或rwlock指定的值无效。138多线程编程指南•2006年10月 使用读写锁读取非阻塞读写锁中的锁pthread_rwlock_tryrdlock(3C)应用读锁的方式与pthread_rwlock_rdlock()类似,区别在于如果任何线程持有rwlock中的写锁或者写入器基于rwlock阻塞,则pthread_rwlock_tryrdlock()函数会失败。对于Solaris线程,请参见第195页中的“rw_tryrdlock语法”。pthread_rwlock_tryrdlock语法#includeintpthread_rwlock_tryrdlock(pthread_rwlock_t*rwlock);pthread_rwlock_tryrdlock返回值如果获取了用于在rwlock所引用的读写锁对象中执行读取的锁,则pthread_rwlock_tryrdlock()将返回零。如果没有获取该锁,则返回用于指明错误的错误号。EBUSY描述:无法获取读写锁以执行读取,因为写入器持有该锁或者基于该锁已阻塞。写入读写锁中的锁pthread_rwlock_wrlock(3C)可用来向rwlock所引用的读写锁应用写锁。pthread_rwlock_wrlock语法#includeintpthread_rwlock_wrlock(pthread_rwlock_t*rwlock);如果没有其他读取器线程或写入器线程持有读写锁rwlock,则调用线程将获取写锁。否则,调用线程将阻塞。调用线程必须获取该锁之后,才能从pthread_rwlock_wrlock()调用返回。如果在进行调用时,调用线程持有读写锁(读锁或写锁),则结果是不确定的。为避免写入器资源匮乏,允许在多个实现中使写入器的优先级高于读取器。(例如,Solaris线程实现允许写入器的优先级高于读取器。请参见第195页中的“rw_wrlock语法”。)如果针对未初始化的读写锁调用pthread_rwlock_wrlock(),则结果是不确定的。第4章•用同步对象编程139 使用读写锁线程信号处理程序可以处理传送给等待读写锁以执行写入的线程的信号。从信号处理程序返回后,线程将继续等待读写锁以执行写入,就好像线程未中断一样。pthread_rwlock_wrlock返回值如果获取了用于在rwlock所引用的读写锁对象中执行写入的锁,则pthread_rwlock_rwlock()将返回零。如果没有获取该锁,则返回用于指明错误的错误号。写入非阻塞读写锁中的锁pthread_rwlock_trywrlock(3C)应用写锁的方式与pthread_rwlock_wrlock()类似,区别在于如果任何线程当前持有用于读取和写入的rwlock,则pthread_rwlock_trywrlock()函数会失败。对于Solaris线程,请参见第196页中的“rw_trywrlock语法”。pthread_rwlock_trywrlock语法#includeintpthread_rwlock_trywrlock(pthread_rwlock_t*rwlock);如果针对未初始化的读写锁调用pthread_rwlock_trywrlock(),则结果是不确定的。线程信号处理程序可以处理传送给等待读写锁以执行写入的线程的信号。从信号处理程序返回后,线程将继续等待读写锁以执行写入,就好像线程未中断一样。pthread_rwlock_trywrlock返回值如果获取了用于在rwlock引用的读写锁对象中执行写入的锁,则pthread_rwlock_trywrlock()将返回零。否则,将返回用于指明错误的错误号。EBUSY描述:无法为写入获取读写锁,因为已为读取或写入锁定该读写锁。解除锁定读写锁pthread_rwlock_unlock(3C)可用来释放在rwlock引用的读写锁对象中持有的锁。pthread_rwlock_unlock语法#include140多线程编程指南•2006年10月 使用读写锁intpthread_rwlock_unlock(pthread_rwlock_t*rwlock);如果调用线程未持有读写锁rwlock,则结果是不确定的。对于Solaris线程,请参见第196页中的“rw_unlock语法”。如果通过调用pthread_rwlock_unlock()来释放读写锁对象中的读锁,并且其他读锁当前由该锁对象持有,则该对象会保持读取锁定状态。如果pthread_rwlock_unlock()释放了调用线程在该读写锁对象中的最后一个读锁,则调用线程不再是该对象的属主。如果pthread_rwlock_unlock()释放了该读写锁对象的最后一个读锁,则该读写锁对象将处于无属主、解除锁定状态。如果通过调用pthread_rwlock_unlock()释放了该读写锁对象的最后一个写锁,则该读写锁对象将处于无属主、解除锁定状态。如果pthread_rwlock_unlock()解除锁定该读写锁对象,并且多个线程正在等待获取该对象以执行写入,则通过调度策略可确定获取该对象以执行写入的线程。如果多个线程正在等待获取读写锁对象以执行读取,则通过调度策略可确定等待线程获取该对象以执行写入的顺序。如果多个线程基于rwlock中的读锁和写锁阻塞,则无法确定读取器和写入器谁先获得该锁。如果针对未初始化的读写锁调用pthread_rwlock_unlock(),则结果是不确定的。pthread_rwlock_unlock返回值如果成功,pthread_rwlock_unlock()会返回零。否则,将返回用于指明错误的错误号。销毁读写锁pthread_rwlock_destroy(3C)可用来销毁rwlock引用的读写锁对象并释放该锁使用的任何资源。pthread_rwlock_destroy语法#includeintpthread_rwlock_destroy(pthread_rwlock_t*rwlock);pthread_rwlock_trwlock=PTHREAD_RWLOCK_INITIALIZER;在再次调用pthread_rwlock_init()重新初始化该锁之前,使用该锁所产生的影响是不确定的。实现可能会导致pthread_rwlock_destroy()将rwlock所引用的对象设置为无效值。如果在任意线程持有rwlock时调用pthread_rwlock_destroy(),则结果是不确定第4章•用同步对象编程141 跨进程边界同步的。尝试销毁未初始化的读写锁会产生不确定的行为。已销毁的读写锁对象可以使用pthread_rwlock_init()来重新初始化。销毁读写锁对象之后,如果以其他方式引用该对象,则结果是不确定的。对于Solaris线程,请参见第197页中的“rwlock_destroy语法”。pthread_rwlock_destroy返回值如果成功,pthread_rwlock_destroy()会返回零。否则,将返回用于指明错误的错误号。EINVAL描述:attr或rwlock指定的值无效。跨进程边界同步每个同步元语都可以跨进程边界使用。通过确保同步变量位于共享内存段中,并调用适当的init()例程,可设置元语。元语必须已经初始化,并且其共享属性设置为在进程间使用。生成方和使用者问题示例示例4–17说明了位于不同进程中的生成方和使用者的问题。主例程将与其子进程共享的全零内存段映射到其地址空间。创建子进程是为了运行使用者,父进程则运行生成方。本示例还说明了生成方和使用者的驱动程序。producer_driver()可从stdin读取字符并调用producer()。consumer_driver()通过调用consumer()来获取字符并将这些字符写入stdout中。示例4–17中的数据结构与示例4–4中所示用于条件变量示例的结构类似。两个信号分别空缓冲区和满缓冲区的数量,通过这些信号可确保生成方等待缓冲区变空,使用者等待缓冲区变满为止。示例4–17跨进程边界同步main(){intzfd;buffer_t*buffer;pthread_mutexattr_tmattr;142多线程编程指南•2006年10月 跨进程边界同步示例4–17跨进程边界同步(续)pthread_condattr_tcvattr_less,cvattr_more;zfd=open("/dev/zero",O_RDWR);buffer=(buffer_t*)mmap(NULL,sizeof(buffer_t),PROT_READ|PROT_WRITE,MAP_SHARED,zfd,0);buffer->occupied=buffer->nextin=buffer->nextout=0;pthread_mutex_attr_init(&mattr);pthread_mutexattr_setpshared(&mattr,PTHREAD_PROCESS_SHARED);pthread_mutex_init(&buffer->lock,&mattr);pthread_condattr_init(&cvattr_less);pthread_condattr_setpshared(&cvattr_less,PTHREAD_PROCESS_SHARED);pthread_cond_init(&buffer->less,&cvattr_less);pthread_condattr_init(&cvattr_more);pthread_condattr_setpshared(&cvattr_more,PTHREAD_PROCESS_SHARED);pthread_cond_init(&buffer->more,&cvattr_more);if(fork()==0)consumer_driver(buffer);else第4章•用同步对象编程143 跨进程边界同步示例4–17跨进程边界同步(续)producer_driver(buffer);}voidproducer_driver(buffer_t*b){intitem;while(1){item=getchar();if(item==EOF){producer(b,‘’);break;}elseproducer(b,(char)item);}}voidconsumer_driver(buffer_t*b){charitem;while(1){if((item=consumer(b))==’’)break;putchar(item);144多线程编程指南•2006年10月 比较元语示例4–17跨进程边界同步(续)}}比较元语线程中最基本的同步元语是互斥锁。因此,在内存使用和执行时间这两个方面,互斥锁都是最高效的机制。互斥锁的基本用途是按顺序访问资源。线程中第二高效的元语是条件变量。条件变量的基本用途是基于状态的变化进行阻塞。条件变量可提供线程等待功能。请注意,线程在基于条件变量阻塞之前必须首先获取互斥锁,在从pthread_cond_wait()返回之后必须解除锁定互斥锁。线程还必须在状态发生改变期间持有互斥锁,然后才能对pthread_cond_signal()进行相应的调用。信号比条件变量占用更多内存。由于信号变量基于状态而非控制来工作,因此在某些情况下更易于使用。与锁不同,信号没有属主。任何线程都可以增加已阻塞的信号。通过读写锁,可以对受保护的资源进行并发读取和独占写入。读写锁是可以在读取或写入模式下锁定的单一实体。要修改资源,线程必须首先获取互斥写锁。必须释放所有读锁之后,才允许使用互斥写锁。第4章•用同步对象编程145 146 第55章使用Solaris软件编程本章介绍多线程与Solaris软件的交互方式以及软件经过更改后支持多线程的方式。■第152页中的“进程创建:exec和exit问题”■第152页中的“计时器、报警与剖析”■第154页中的“非本地转向:setjmp和longjmp”■第154页中的“资源限制”■第154页中的“LWP和调度类”■第156页中的“扩展传统信号”■第166页中的“I/O问题”进程创建中的fork问题Solaris9产品和更早Solaris发行版中处理fork()的缺省方式与在POSIX线程中处理fork()的方式稍有不同。对于Solaris9之后的Solaris发行版,在所有情况下,fork()都会按照为POSIX线程指定的方式工作。表5–1对在Solaris线程和pthread中处理fork()的相似与不同之处进行了比较。当可比较的接口在POSIX线程或Solaris线程中不可用时,‘—'字符将出现在表列中。表5–1比较POSIX与Solarisfork()的处理Solaris接口POSIX线程接口Fork-one模型fork1(2)fork(2)Fork-all模型forkall(2)forkall(2)fork安全性—pthread_atfork(3C)147 进程创建中的fork问题Fork-One模型如表5–1所示,pthreadfork(2)函数的行为与Solarisfork1(2)函数的行为相同。pthreadfork(2)函数和Solarisfork1(2)函数都将创建新的进程,并将完整的地址空间复制到子进程中。但是,这两个函数都只将调用线程复制到子进程中。当子进程直接调用exec()时,将调用线程复制到子进程中非常有用,大多数情况下,此操作发生在对fork()的调用之后。在这种情况下,子进程不需要复制fork()以外的任何线程。在子进程中,调用fork()之后和调用exec()之前,请不要调用任何库函数。某个库函数可能会使用父进程在调用fork()时所持有的锁。调用某个exec()处理程序之前,子进程可能仅执行异步信号安全操作。Fork-One安全问题和解决方案除了通常关注的问题(如锁定共享数据)以外,当只有fork()线程处于运行状态时,还应根据fork子进程的操作来处理库。问题在于子进程中的唯一线程可能会尝试获取由未复制到子进程中的线程持有的锁定。大多数程序不可能遇到此问题。从fork()返回后,大多数程序都会调用子进程中的exec()。但是,如果程序在调用exec()之前必须在子进程中执行操作,或永远不会调用exec(),则子进程可能会遇到死锁。每个库编写者都应提供安全的解决方案,尽管提供一个非fork安全的库不是一个很大的问题。例如,假设当T2fork新进程时,T1在进行打印,且对printf()持有锁定。在子进程中,如果唯一的线程(T2)调用printf(),则T2将快速死锁。POSIXfork()或Solarisfork1()函数仅复制用于调用fork()或fork1()的线程。如果调用Solarisforkall()来复制所有线程,则此问题不是要关注的问题。但是,forkall()可能会导致其他问题,使用时应小心。例如,如果一个线程调用forkall(),则将在子进程中复制对文件执行I/O的父线程。线程的两个副本都将继续对同一个文件执行I/O,一个副本在父进程中,一个副本在子进程中,这将导致出现异常或文件损坏。要防止在调用fork1()时出现死锁,请确保在执行fork时任何锁定都未被持有。防止死锁的最有效的方式就是让fork线程获取可能由子进程使用的所有锁定。由于无法获取对printf()的所有锁定(由于printf()由libc所有),因此必须确保在使用fork()时没有使用printf()。要管理库中的锁定,应执行以下操作:■确定库使用的所有锁定。■确定库所使用锁定的锁定顺序。如果没有使用严格的锁定顺序,则必须谨慎管理锁定获取。■安排在fork时获取所有锁定。148多线程编程指南•2006年10月 进程创建中的fork问题在以下示例中,库使用的锁定列表为{L1,...Ln}。这些锁定的锁定顺序也为L1...Ln。mutex_lock(L1);mutex_lock(L2);fork1(...);mutex_unlock(L1);mutex_unlock(L2);使用Solaris线程或POSIX线程时,应该在库的.init()部分中添加对pthread_atfork(f1,f2,f3)的调用。f1()、f2()、f3()定义如下:f1()/*Thisisexecutedjustbeforetheprocessforks.*/{mutex_lock(L1);|mutex_lock(...);|--orderedinlockordermutex_lock(Ln);|}Vf2()/*Thisisexecutedinthechildaftertheprocessforks.*/{mutex_unlock(L1);mutex_unlock(...);mutex_unlock(Ln);}f3()/*Thisisexecutedintheparentaftertheprocessforks.*/{mutex_unlock(L1);第5章•使用Solaris软件编程149 进程创建中的fork问题mutex_unlock(...);mutex_unlock(Ln);}虚拟fork-vfork标准vfork(2)函数在多线程程序中并不安全。vfork(2)(与fork1(2)¤@样)仅复制子进程中的调用线程。就像在非线程实现中一样,vfork()不会复制子进程的地址空间。请注意,子进程中的线程在调用exec(2)之前不会更改内存。vfork()为子进程提供父地址空间。子进程调用exec()或退出之后,父进程将取回其地址空间。子进程不得更改父进程的状态。例如,如果在对vfork()的调用与对exec()的调用之间创建新的线程,则会出现灾难性问题。解决方案:pthread_atfork使用Fork-One模型时,请使用pthread_atfork()来防止死锁。#includeintpthread_atfork(void(*prepare)(void),void(*parent)(void),void(*child)(void));pthread_atfork()函数声明了在调用fork()的线程的上下文中的fork()前后调用的fork()处理程序。■在fork()启动前调用prepare处理程序。■在父进程中返回fork()后调用parent处理程序。■在子进程中返回fork()后调用child处理程序。可以将任何处理程序参数都设置为NULL。对pthread_atfork()进行连续调用的顺序非常重要。例如,prepare处理程序可能会获取所有需要的互斥。然后,parent和child处理程序可能会释放互斥。获取所有需要的互斥的prepare处理程序可确保在对进程执行fork之前,所有相关的锁定都由调用fork函数的线程持有。此技术可防止子进程中出现死锁。150多线程编程指南•2006年10月 进程创建中的fork问题pthread_atfork返回值调用成功完成后,pthread_atfork()将返回零。其他任何返回值都表示出现了错误。如果检测到以下情况,pthread_atfork()将失败并返回对应的值。ENOMEM描述:用于记录fork处理程序地址的表空间不足。Fork-all模型Solarisforkall(2)函数可以复制地址空间以及子进程中的所有线程。地址空间复制非常有用,例如,在子进程永远不调用exec(2)但会使用其父地址空间的副本时。当进程中的某个线程调用Solarisforkall(2)时,在可中断的系统调用中阻塞的线程将返回EINTR。请注意,不要创建同时由父进程和子进程持有的锁定。通过调用包含MAP_SHARED标志的mmap()在共享内存中分配锁定时,会出现父进程和子进程同时持有锁定的情况。如果使用Fork-One模型,则不会出现此问题。选择正确的Fork从Solaris10发行版开始,对fork()的调用与对fork1()的调用相同。具体来说,在子进程中仅复制调用线程。此行为与POSIXfork()的行为相同。在以前的Solaris软件发行版中,fork()的行为取决于应用程序是否与POSIX线程库相链接。如果与-lthread(Solaris线程)链接,但没有与-lpthread(POSIX线程)链接,则fork()与forkall()相同。如果与-lpthread链接,无论fork()是否还与-lthread链接,fork()都与fork1()相同。从Solaris10发行版开始,多线程不需要-lthread和-lpthread。标准C库为两组应用程序程序接口提供所有的线程支持。需要复制所有fork语义的应用程序必须调用forkall()。调用任何fork()函数后,使用全局状态时要非常小心。例如,当一个线程连续读取文件,而进程中的另一个线程成功fork时,每个进程都包含读取该文件的线程。由于在调用fork()后会共享文件描述符的查找指针,因此在子线程获取数据的同时,父进程会获取不同的数据。由于父线程和子线程将获取不同的数据,因此会给连续读取访问带来间隙。第5章•使用Solaris软件编程151 进程创建:exec和exit问题进程创建:exec和exit问题exec(2)和exit(2)系统调用的工作方式与这些函数在单线程进程中的工作方式相同,但以下情况例外。在多线程应用程序中,这些函数将销毁地址空间中的所有线程。销毁所有执行资源和所有活动线程之前,这两个调用将阻塞。exec()重新生成进程时,exec()将创建单个轻量进程(lightweightprocess,LWP)。进程启动代码将生成初始线程。通常,如果初始线程返回,则该线程将调用exit(),且进程将被销毁。当进程中的所有线程都退出时,进程将退出。从包含多个线程的进程中调用任何exec()函数时将终止所有线程,并装入和执行新的可执行映像。但不会调用destructor函数。计时器、报警与剖析在Solaris2.5发行版中,已针对每LWP计时器和每线程报警声明了“生命周期结束时间”。请参见timer_create(3RT)、alarm(2)或setitimer(2)手册页。现在,每LWP计时器和每线程报警都被替换为每进程变体,这些内容在本节中加以介绍。最初,每个LWP都有唯一的实时时间间隔计时器和报警,与LWP绑定的线程可以使用该计时器和报警。当计时器或报警过期时,会向线程传送一个信号。每个LWP还有一个虚拟时间计时器或配置文件时间间隔计时器,与LWP绑定的线程可以使用这些计时器。当时间间隔计时器过期时,系统会根据需要将SIGVTALRM或SIGPROF发送到拥有该时间间隔计时器的LWP。每LWPPOSIX计时器在Solaris2.3和2.4发行版中,timer_create(3RT)函数返回一个包含计时器ID的计时器对象,该ID仅在调用LWP中有意义。到期信号将被传送到该LWP。由于返回的计数器对象的行为,只有绑定线程可以使用POSIX计时器工具。即使使用受到限制,Solaris2.3和2.4发行版中多线程应用程序的POSIX计时器也不可靠。这些计时器不能可靠地屏蔽生成的信号,也不能可靠地传送sigvent结构中的关联值。在Solaris2.5发行版中引入的应用程序可以创建每进程计时器。编译应用时定义了_POSIX_PER_PROCESS_TIMERS宏,或通过使用大于或等于199506L的值定义宏_POSIX_C_SOURCE来编译应用程序。从Solaris9发行版起生效,所有的计时器都针对每个进程,但虚拟时间计时器和配置文件时间间隔计时器除外,它们仍然针对每个LWP。有关ITIMER_VIRTUAL和ITIMER_PROF,请参见setitimer(2)。152多线程编程指南•2006年10月 计时器、报警与剖析每进程计时器的计时器ID在任何LWP中都可用。系统将针对进程(而非针对特定LWP)生成到期信号。只能通过timer_delete(3RT)或在进程终止时删除每进程计时器。每线程报警在Solaris2.3和2.4发行版中,对alarm(2)或setitimer(2)的调用仅在调用LWP中有意义。LWP创建终止时,将自动删除这类计时器。由于此行为,只有alarm()或setitimer()可以使用绑定线程。即使限于使用绑定线程,Solaris2.3和2.4多线程应用程序中的alarm()和setitimer()计时器也不可靠。特别是,在从发出这些调用的绑定线程屏蔽信号方面,alarm()和settimer()计时器不可靠。如果不需要这类屏蔽,则这两个系统调用可在绑定线程中可靠地工作。在Solaris2.5发行版中,调用alarm()时,与-lpthread(POSIX)线程链接的应用程序将获取每个进程传送的SIGALRM。alarm()生成的SIGALRM是针对进程生成,而不是针对特定LWP生成。另外,进程终止时,将重置报警。使用Solaris2.5之前的发行版编译的应用程序或没有与-lpthread链接的应用程序将继续查看每个LWP传送的信号,这些信号是由alarm()和setitimer()生成的。对alarm()或setitimer(ITIMER_REAL)的调用将导致生成的SIGALRM信号被发送到进程(从Solaris9发行版开始生效)。剖析多线程程序在Solaris2.6以前的Solaris发行版中,在多线程程序中调用profil()仅影响调用LWP。创建LWP时不会继承配置文件状态。要使用全局配置文件缓冲区来配置多线程程序,线程启动时每个线程都需要调用profil()。此外,每个线程必须为绑定线程。这些限制很麻烦。它们不能顺利支持动态打开和关闭剖析。在Solaris2.6以及更高发行版中,对多线程进程的profil()系统调用具有全局影响力。对profil()的调用将影响进程中的所有LWP和线程。profil()可能会使与以前的每LWP语义相关的应用程序中断。但是,预计调用profil()可以改进需要在运行时动态打开和关闭剖析的多线程程序。第5章•使用Solaris软件编程153 非本地转向:setjmp和longjmp非本地转向:setjmp和longjmpsetjmp()和longjmp()的范围限于一个线程,该线程在大多数时间是可接受的。但是,限制的范围意味着只有在同一个线程中执行setjmp()时,处理信号的线程才能执行longjmp()。资源限制资源限制是对整个进程设置的,且通过添加进程中所有线程的资源使用来加以确定。超过软资源限制时,会向违例线程发送相应的信号。可通过getrusage(3C)使用进程中所使用的所有资源。LWP和调度类Solaris内核具有三种调度类。优先级最高的调度类是实时(RT)类。优先级居中的调度类是system。不能将system类应用于用户进程。优先级最低的调度类为分时(TS)类,也是缺省类。系统将针对每个LWP维护调度类。创建进程时,初始LWP将继承调度类和在父进程中创建LWP的优先级。随着所创建进程数目的增多,其关联的LWP也会继承此调度类和优先级。线程具有其基础LWP的调度类和优先级。进程中的每个LWP都有内核可见的唯一调度类和优先级。线程优先级可以控制同步对象的争用情况。缺省情况下,LWP处于分时类中。对于与计算绑定的多线程,线程优先级不是非常有用。对于使用MT库频繁执行同步的多线程应用程序,线程优先级更有意义。调度类是由priocntl(2)设置的。指定前两个参数的方式确定只有调用LWP还是一个或多个进程的所有LWP受影响。priocntl()的第三个参数是命令,可以是以下命令之一。■PC_GETCID-获取特定类的类ID和类属性。■PC_GETCLINFO-获取特定类的类名称和类属性。■PC_GETPARMS-获取进程、包含进程的LWP或一组进程的类标识符和类特定调度参数。■PC_SETPARMS-设置进程、包含进程的LWP或一组进程的类标识符和类特定调度参数。请注意,priocntl()会影响与调用线程关联的LWP的调度。对于未绑定线程,返回对priocntl()的调用后,无法保证调用线程与受影响的LWP关联。154多线程编程指南•2006年10月 LWP和调度类分时调度分时调度可以在分时调度类的LWP中公平地分布处理资源。内核的其他部分可以在短时间内独占处理器,而不会缩短用户察觉的响应时间。priocntl(2)调用可以设置一个或多个进程的nice(2)级别。priocntl()调用还会影响进程中所有分时类LWP的nice()级别。nice()级别的范围通常为0到+20,对于具有超级用户权限的进程,该范围为-20到+20。值越低,优先级越高。分时LWP的分发优先级是根据LWP的即时CPU使用率及其nice()级别计算出来的。nice()级别指示LWP相对于分时调度程序的优先级。nice()值越大的LWP获得的总处理份额越小,但都为非零值。接收处理量较大的LWP的优先级与接收处理量很少或没有接收任何处理的LWP的优先级要低。实时调度可以将实时类(RT)应用于整个进程或应用于进程中的一个或多个LWP。必须具有超级用户权限才能使用实时类。与分时类的nice(2)级别不同,可以分别或联合为分类为实时类的LWP指定优先级。priocntl(2)调用将影响进程中所有实时LWP的属性。调度程序始终会分发优先级最高的实时LWP。当优先级较高的LWP可以运行时,优先级高的实时LWP优先于优先级较低的LWP。优先的LWP置于其级别队列的开头。实时LWP始终控制着处理器,直到优先处理了LWP、LWP暂停或更改了其实时优先级为止。RT类的LWP绝对优先于TS类中的进程。新的LWP将继承父进程或LWP的调度类。RT类LWP将继承父进程的时间¤ù,无论是有限的还是无限的。有限的时间片LWP将始终运行,直到LWP终止、中断了I/O事件、优先级较高的可运行实时进程优先于该LWP执行或时间片到期为止。只有在LWP终止、中断或其他实时进程优先于该LWP执行,时间片无限的LWP才停止执行操作。公平共享调度程序公平共享调度程序(fairsharescheduler,FSS)调度类允许根据份额来分配CPU时间。缺省情况下,FSS调度类与TS和交互式(interactive,IA)调度类使用相同的优先级范围(0到59)。进程中的所有LWP必须在同一调度类中运行。FSS类将调度单个LWP,而不是整个进程。因此,混合使用FSS和TS/IA类中的进程可能会导致在这两种情况下出现意外的调度行为。第5章•使用Solaris软件编程155 扩展传统信号TS/IA或FSS调度类进程不会争用相同的CPU。处理器集可以在系统中混合TS/IA与FSS。但是,每个处理器集中的所有进程都必须属于TS/IA调度类或FSS调度类。固定优先级调度FX(固定优先级)调度类可以指定没有为适应资源占用而调整的固定优先级和时间量程。进程优先级只能由指定优先级的进程或具有适当权限的进程进行更改。有关FX的更多信息,请参见priocntl(1)和dispadmin(1M)手册页。此类中的线程与TS和交互式(interactive,IA)调度类共享相同的优先级范围(0到59)。TS通常为缺省调度类。FX通常与TS结合使用。扩展传统信号传统的UNIX信号模型通过相当自然的方式扩展到线程。关键特征是信号是在进程范围内部署的,而信号掩码是针对每个进程部署的。信号的进程范围部署是使用传统的机制(signal(3C)、sigaction(2)等)建立的。当信号处理程序标记为SIG_DFL或SIG_IGN时,将对整个接收进程执行信号接收操作。这些信号包括退出、核心转储、停止、继续和忽略。系统将针对进程中的所有线程执行这些信号的接收操作。因此,不存在哪个线程拾取信号的问题。退出、核心转储、停止、继续和忽略信号都没有处理程序。有关信号的基本信息,请参见signal(5)。每个线程都有自己的信号掩码。当线程使用的内存或状态同时也被信号处理程序使用时,可通过信号掩码来阻塞某些信号。进程中的所有线程都共享由sigaction(2)及其变体设置的一组信号处理程序。一个进程中的线程不能将信号发送到另一个进程中的特定线程。通过kill(2)、sigsend(2)或sigqueue(3RT)发送到进程的信号由该进程中任何接收线程来处理。信号被分为以下类别:陷阱、异常和中断。异常是以同步方式生成的信号。陷阱和中断是以异步方式生成的信号。就像在传统的UNIX中一样,如果信号处于暂挂状态,则该信号的其他实例通常没有其他影响。暂挂信号由位表示,而不是由计数器表示。但是,通过sigqueue(3RT)接口发送的信号允许在进程中对同一信号的多个实例排队。对于单线程进程而言,线程接收信号时如果被阻塞在系统调用中,则该线程可能很早就会返回。如果线程很早就返回,则该线程会返回EINTR错误代码,或在I/O调用中传输的字节数比请求的字节数要少。对多线程程序特别重要的一点就是信号对pthread_cond_wait(3C)产生的影响。此调用通常仅针对pthread_cond_signal(3C)或pthread_cond_broadcast(3C)返回零,而不会出现任何错误。但是,如果等待线程接收传统的UNIX信号,则pthread_cond_wait()将返回零,即使唤醒是虚假的也是如此。在这种情况下,Solaris线程cond_wait(3C)函数将返回EINTR。有关更多信息,请参见第165页中的“中断对条件变量的等待”。156多线程编程指南•2006年10月 扩展传统信号同步信号陷阱(如SIGILL、SIGFPE和SIGSEGV)是由于对线程执行操作引起的,如除以零或引用不存在的内存。陷阱仅由导致陷阱的线程处理。进程中的多个线程可以同时生成和处理同种类型的陷阱。可以很容易地针对同时生成的信号将信号扩展到各个线程。可以针对生成同步信号的线程调用处理程序。但是,如果进程选择不建立相应的信号处理程序,则出现陷阱时将执行缺省操作。即使针对生成的信号阻塞违例线程,也会执行缺省操作。这类信号的缺省操作是终止进程,可能还会进行核心转储。这类同步信号通常意味着整个进程出现严重问题,而不仅仅是线程出现问题。在这种情况下,终止进程通常是很好的选择。异步信号中断(如SIGINT和SIGIO)与任何线程都是异步的,而且是由进程外的某些操作引起的。这些中断可能是其他进程显式发送的信号,也可能代表外部操作(如用户键入Ctrl-C组合键)。中断可由信号掩码允许中断的任何线程来处理。即使有多个线程可以接收中断,也只能选择一个线程。将同一信号的多个实例发送到进程时,每个实例都可由单独的线程来处理。但是,可用线程不得屏蔽信号。当所有的线程都屏蔽信号时,信号将被标记为暂挂,而由取消屏蔽信号的第一个线程来处理信号。延续语义延续语义是传统的处理信号的方式。信号处理程序返回时,将控制恢复进程在中断时所处的位置。此控制恢复非常适合于单线程进程中的异步信号,如示例5–1所示。在其他编程语言(如PL/1)中,此控制恢复还用作异常处理机制。示例5–1延续语义unsignedintnestcount;unsignedintA(inti,intj){第5章•使用Solaris软件编程157 扩展传统信号示例5–1延续语义(续)nestcount++;if(i==0)return(j+1)elseif(j==0)return(A(i-1,1));elsereturn(A(i-1,A(i,j-1)));}voidsig(inti){printf("nestcount=%d ",nestcount);}main(){sigset(SIGINT,sig);A(4,4);}对信号执行的操作本节介绍对信号执行的操作。第159页中的“设置线程的信号掩码”第159页中的“将信号发送到特定线程”第159页中的“等待指定信号”第160页中的“在给定时间内等待指定的信号”158多线程编程指南•2006年10月 扩展传统信号设置线程的信号掩码pthread_sigmask(3C)对线程执行sigprocmask(2)对进程所执行的操作。pthread_sigmask()可以设置thread的信号掩码。创建新的线程时,其初始掩码是从其创建者继承的。在多线程进程中对sigprocmask()执行调用等效于对pthread_sigmask()执行调用。有关更多信息,请参见sigprocmask(2)手册页。将信号发送到特定线程pthread_kill(3C)是kill(2)的线程模拟。pthread_kill()可以将信号发送到特定线程。发送到指定线程的信号不同于发送到进程的信号。将信号发送到进程时,信号可由该进程中的任何线程来处理。通过pthread_kill()发送的信号只能由指定线程来处理。可以使用pthread_kill()将信号仅发送到当前进程中的线程。由于thread_t类型的线程标识符的范围是本地,因此不能指定当前进程范围以外的线程。通过目标线程接收信号时,调用的操作(处理程序SIG_DFL或SIG_IGN)通常为全局操作。如果将SIGXXX发送到线程,且SIGXXX的作用是中止进程,则目标线程接收信号时将中止整个进程。等待指定信号对于多线程程序,sigwait(2)是可供使用的首选接口,因为sigwait()可以很好地处理异步生成的信号。sigwait()将导致调用线程等待,直到由其设置参数标识的任何信号被传送到该线程为止。线程等待的同时,系统将取消屏蔽由设置参数标识的信号,但调用返回时将恢复原始掩码。由设置参数标识的所有信号必定会在所有线程(包括调用线程)上受到阻塞。否则,sigwait()可能无法正常工作。使用sigwait()将线程与异步信号分离。创建一个侦听异步信号的线程时,可同时创建其他线程来阻塞为此进程设置的任何异步信号。从Solaris2.5发行版开始,可以使用sigwait()的两个版本:Solaris2.5版本和POSIX标准版本。新的应用程序和新的库应该使用POSIX标准接口,因为Solaris版本在未来的发行版中可能不可用。以下示例显示了sigwait()的两个版本的语法:#include第5章•使用Solaris软件编程159 扩展传统信号/*theSolaris2.5version*/intsigwait(sigset_t*set);/*thePOSIXstandardversion*/intsigwait(constsigset_t*set,int*sig);传送信号时,POSIXsigwait()将清除暂挂信号,并将信号数字置于sig中。许多线程可以同时调用sigwait(),但是针对每个接收的信号仅返回一个线程。借助sigwait(),可以同时处理异步信号。信号到达后,处理这类信号的线程将立即调用sigwait()并返回。通过确保所有线程(包括sigwait()的调用程序)都屏蔽异步信号,可确保信号仅由预期处理程序处理,且安全地进行处理。通过始终屏蔽所有线程中的所有信号并在必要时调用sigwait(),可以使应用程序中依赖于信号的线程的安全性大大提高。通常,可以创建一个或多个为等待信号而调用sigwait()的线程。由于sigwait()甚至会检索屏蔽的信号,因此一定要阻塞所有其他线程中的重要信号,以便不会意外传送这些信号。信号到达时,线程将从sigwait()返回,处理信号,并再次调用sigwait()以等待更多信号。信号处理线程并不限于使用异步信号安全函数。信号处理线程可采用通常的方式与其他线程同步。第173页中的“MT接口安全级别”定义了异步信号安全类别。注–sigwait()不能接收同步生成的信号。在给定时间内等待指定的信号sigtimedwait(3RT)类似于sigwait(2),但在指定的时间内没有收到信号时,sigtimedwait()将失败并返回错误。定向于线程的信号借助定向于线程的信号的概念,对UNIX信号机制得到了扩展。定向于线程的信号就像普通的异步信号一样,但定向于线程的信号是被发送到特定线程,而不是进程。与安装用于处理信号的信号处理程序相比,使用等待异步信号的单独线程可能更安全且更简单。一种处理异步信号的更好方式是同步处理这些信号。通过调用sigwait(2),线程可以一直等待,直到信号出现为止。请参见第159页中的“等待指定信号”。160多线程编程指南•2006年10月 扩展传统信号示例5–2异步信号和sigwait(2)main(){sigset_tset;voidrunA(void);intsig;sigemptyset(&set);sigaddset(&set,SIGINT);pthread_sigmask(SIG_BLOCK,&set,NULL);pthread_create(NULL,0,runA,NULL,PTHREAD_DETACHED,NULL);while(1){sigwait(&set,&sig);printf("nestcount=%d ",nestcount);printf("receivedsignal%d ",sig);}}voidrunA(){A(4,4);exit(0);}本示例将修改示例5–1的代码。主例程将屏蔽SIGINT信号,创建一个子线程(用于调用前一个示例的函数A),并发出sigwait()来处理SIGINT信号。第5章•使用Solaris软件编程161 扩展传统信号请注意,信号在计算线程中将被屏蔽,因为计算线程将从主线程继承其信号掩码。当且仅当主线程在sigwait()内部不受阻塞时,才能受到保护,而不去处理SIGINT。另外,请注意,使用sigwait()时不存在中断系统调用的危险。完成语义另一种处理信号的方式是使用完成语义。当信号指明发生灾难性情况,导致没有理由继续执行当前代码块时,请使用完成语义。信号处理程序将代替其余有问题的块运行。换句话说,信号处理程序将完成该块。在示例5–3中,所讨论的块是if语句的then部分的主体。对setjmp(3C)的调用会在jbuf中保存程序的当前寄存器状态并返回0,从而执行块。示例5–3完成语义sigjmp_bufjbuf;voidmult_divide(void){inta,b,c,d;voidproblem();sigset(SIGFPE,problem);while(1){if(sigsetjmp(&jbuf)==0){printf("Threenumbers,please: ");scanf("%d%d%d",&a,&b,&c);d=a*b/c;printf("%d*%d/%d=%d ",a,b,c,d);}}}162多线程编程指南•2006年10月 扩展传统信号示例5–3完成语义(续)voidproblem(intsig){printf("Couldn’tdealwiththem,tryagain ");siglongjmp(&jbuf,1);}如果出现SIGFPE浮点异常,则系统将调用信号处理程序。信号处理程序将调用siglongjmp(3C)(用于恢复jbuf中保存的寄存器状态),进而导致程序再次从sigsetjmp()返回。保存的寄存器包括程序计数器和栈指针。但是,此时sigsetjmp(3C)将返回siglongjmp()的第二个参数(值为1)。请注意,块将被跳过,而仅在下一次迭代while循环期间执行。可以在多线程程序中使用sigsetjmp(3C)和siglongjmp(3C)。请注意,一个线程永远不会执行使用另一个线程的sigsetjmp()结果的siglongjmp()。此外,sigsetjmp()和siglongjmp()可以恢复和保存信号掩码,而setjmp(3C)和longjmp(3C)不会执行这些操作。使用信号处理程序时,请使用sigsetjmp()和siglongjmp()。TM完成语义通常用于处理异常。需要特别指出的是,SunAda编程语言就使用此模型。注–请记住,不得将sigwait(2)与同步信号一同使用。信号处理程序和异步信号安全与线程安全类似的概念就是异步信号安全。异步信号安全操作可保证不会干扰正被中断的操作。当信号处理程序操作干扰正被中断的操作时,就会引发异步信号安全问题。例如,假设程序正在调用printf(3S),且其调用程序调用printf()时出现了信号。在这种情况下,两个printf()语句的输出彼此关联。要避免关联输出,当printf()可能被信号中断时,处理程序不应直接调用printf()。无法使用同步元语来解决此问题。在信号处理程序与正被同步的操作之间执行的任何同步尝试都将立即产生死锁现象。第5章•使用Solaris软件编程163 扩展传统信号假设printf()借助互斥来保护自身。现在,假设调用printf()进而通过互斥锁持有锁定的线程被信号中断。如果处理程序调用printf(),则通过互斥锁持有锁定的线程将尝试再次利用互斥锁。尝试利用互斥锁将导致瞬间死锁。为避免处理程序与操作之间出现干扰,请确保这种情况永远不会发生。或许您可以在关键时候屏蔽信号,或从内部信号处理程序中仅调用异步信号安全操作。表5–2中列出了POSIX可确保异步信号安全的仅有例程。任何信号处理程序都可以安全地调用这些函数之一。表5–2异步信号安全函数_exit()fstat()read()sysconf()access()getegid()rename()tcdrain()alarm()geteuid()rmdir()tcflow()cfgetispeed()getgid()setgid()tcflush()cfgetospeed()getgroups()setpgid()tcgetattr()cfsetispeed()getpgrp()setsid()tcgetpgrp()cfsetospeed()getpid()setuid()tcsendbreak()chdir()getppid()sigaction()tcsetattr()chmod()getuid()sigaddset()tcsetpgrp()chown()kill()sigdelset()time()close()link()sigemptyset()times()creat()lseek()sigfillset()umask()dup2()mkdir()sigismember()uname()dup()mkfifo()sigpending()unlink()execle()open()sigprocmask()utime()execve()pathconf()sigsuspend()wait()fcntl()pause()sleep()waitpid()fork()pipe()stat()write()164多线程编程指南•2006年10月 扩展传统信号中断对条件变量的等待将捕获到的已取消屏蔽的信号传送到等待条件变量的线程时,线程将从虚假唤醒的信号处理程序中返回。虚假唤醒是指不是由其他线程中的条件信号调用导致的唤醒。在这种情况下,Solaris线程接口cond_wait()和cond_timedwait()将返回EINTR,而POSIX线程接口pthread_cond_wait()和pthread_cond_timedwait()将返回0。在所有情况下,从条件等待返回之前都将重新获取关联的互斥锁定。重新获取关联的互斥锁定并不暗示线程在执行信号处理程序的同时,互斥处于锁定状态。未定义信号处理程序中的互斥状态。由于在Solaris9发行版之前的Solaris软件发行版中实现了libthread,因而保证了在处于信号处理程序中时保留了互斥。依赖此原有行为的应用程序需要修改Solaris9发行版以及后续发行版。示例5–4将对处理程序清除加以说明。示例5–4条件变量和中断的等待intsig_catcher(){sigset_tset;voidhdlr();mutex_lock(&mut);sigemptyset(&set);sigaddset(&set,SIGINT);sigsetmask(SIG_UNBLOCK,&set,0);if(cond_wait(&cond,&mut)==EINTR){/*signaloccurredandlockisheld*/cleanup();mutex_unlock(&mut);return(0);第5章•使用Solaris软件编程165 I/O问题示例5–4条件变量和中断的等待(续)}normal_processing();mutex_unlock(&mut);return(1);}voidhdlr(){/*stateofthelockisundefined*/...}假设SIGINT信号在进入sig_catcher()时在所有线程中受到阻塞。此外,还假设已通过调用sigaction(2)建立了hdlr()(作为SIGINT信号的处理程序)。如果在线程处于cond_wait()中时将捕获到的已取消屏蔽的SIGINT信号实例传送到该线程,该线程将调用hdlr()。然后,线程将返回到cond_wait()函数(如有必要,将在此处重新获取互斥锁定),并从cond_wait()返回EINTR。是否已针对sigaction()将SA_RESTART指定为标志在此处无影响。cond_wait(3C)不是系统调用,也不会自动重新启动。如果在cond_wait()中阻塞线程时出现捕获的信号,则调用将始终返回EINTR。I/O问题多线程编程的最大优点之一就是可以提升I/O性能。传统的UNIXAPI在这方面给您提供的帮助极少。要么就使用文件系统的功能,要么就整个跳过文件系统。本节说明如何使用线程通过I/O并发性和多缓冲来获得更多灵活性,此外,本节还论述了包含线程的同步I/O与包含和不包含线程的异步I/O的各种方式之间的差异和相似之处。166多线程编程指南•2006年10月 I/O问题I/O作为远程过程调用在传统的UNIX模型中,I/O看上去是同步的,就像对I/O设备进行远程过程调用一样。调用返回后,I/O即完成,或者至少看上去已完成。例如,写入请求可能仅导致将数据传输到操作环境中的缓冲区。此模型的优点在于过程调用的概念是为用户所熟知的。在传统UNIX系统中未使用的一种替代方法是异步模型,在此模型中,I/O请求仅启动操作。程序必须以某种方式了解操作完成的时间。异步模型不像同步模型那样简单。但是,异步模型的优点是允许并发I/O和在传统单线程UNIX进程中的处理。人为的异步性通过在多线程程序中使用同步I/O,可以获得异步I/O的大多数优势。使用异步I/O时,可以发出请求并随后检查以确定I/O完成的时间。可以改用单独的线程同步执行I/O。随后,主线程或许会在以后的某个时间通过调用pthread_join(3C)来检查是否完成了操作。异步I/O在多数情况下,不需要异步I/O,因为其效果可借助线程得以实现,每个线程执行同步I/O。但是,在少数情况下,线程不能实现异步I/O可以实现的效果。最直观的示例就是写入磁带机以形成磁带机流。流形式可防止在向磁带机写入内容的同时磁带机停止运行。磁带将高速向前移动,同时提供写入磁带的连续不断的数据流。要支持流形式,内核中的磁带机应使用线程。内核中的磁带机对中断做出响应时,该磁带机一定会发出排队的写入请求。中断指示以前的磁带写入操作已完成。线程不能保证会对异步写入进行排序,因为线程执行的顺序是不确定的。例如,您无法指定写入磁带的顺序。异步I/O操作#includeintaioread(intfildes,char*bufp,intbufs,off_toffset,第5章•使用Solaris软件编程167 I/O问题intwhence,aio_result_t*resultp);intaiowrite(intfiledes,constchar*bufp,intbufs,off_toffset,intwhence,aio_result_t*resultp);aio_result_t*aiowait(conststructtimeval*timeout);intaiocancel(aio_result_t*resultp);aioread(3AIO)和aiowrite(3AIO)在格式上类似于pread(2)和pwrite(2),但是添加了最后一个参数。对aioread()和aiowrite()的调用导致启动I/O操作或将该操作排入队列。调用将顺利返回,而不被阻塞,而且调用的状态将在resultp所指向的结构中返回。resultp是aio_result_t类型的项,其中包含以下值:intaio_return;intaio_errno;当调用立即失败时,可以在aio_errno中找到失败代码。否则,此字段将包含AIO_INPROGRESS,意味着已成功地将操作排入队列。等待I/O操作完成通过调用aiowait(3AIO),可以等待未完成的异步I/O操作完成。aiowait()将返回指向aio_result_t结构(随原始aioread(3AIO)或原始aiowrite(3)调用一同提供)的指针。此时,aio_result_t将包含read(2)或write(2)?òa?|^ao?H?§,前提是要调用其中一个函数而不是异步版本。如果read()或write()成功,则aio_return包含已读取或写入的字节数。如果read()或write()不成功,则aio_return为-1,且aio_errno包含错误代码。aiowait()将使用timeout参数,该参数指示调用程序将等待的时间。此处的NULL指针表示调用程序将无限期等下去。指向包含零值的结构的指针表示调用程序根本不会等待。您可能会启动异步I/O操作,执行某项工作,然后调用aiowait()以等待请求完成。或者,可以使用SIGIO在操作完成时以异步方式得到通知。168多线程编程指南•2006年10月 I/O问题最后,可通过调用aiocancel()来取消暂挂的异步I/O操作。此例程是使用结果区域地址作为参数来进行调用的。此结果区域标识哪项操作将被取消。共享的I/O和新的I/O系统调用多个线程执行具有相同文件描述符的并发I/O操作时,您可能会发现传统的UNIXI/O接口不是线程安全的。在lseek(2)系统调用设置了文件偏移的位置,会出现不连续I/O问题。随后将在接下来的read(2)或write(2)调用中使用该文件偏移,以指示操作应在文件中的哪个位置开始。当两个或更多线程向同一文件描述符发出lseeks()时,将产生冲突。为避免此冲突,请使用pread(2)和pwrite(2)系统调用。#include#includessize_tpread(intfildes,void*buf,size_tnbyte,off_toffset);ssize_tpwrite(intfiledes,void*buf,size_tnbyte,off_toffset);pread(2)和pwrite(2)的行为方式与read(2)和write(2)非常类似,但是pread(2)和pwrite(2)多使用了一个参数(即文件偏移)。可以使用此参数来指定偏移,而无需使用lseek(2),因此多个线程可以安全地使用这些例程来处理对同一文件描述符的I/O。getc和putc的替代项标准I/O也会出现问题。程序员习惯使用例程(如getc(3C)和putc(3C)),这些例程以宏方式实现,且速度非常快。由于getc(3C)和putc(3C)的速度较快,因此可以在程序的内部循环中使用这些宏,而不必担心效率。但是,将getc(3C)和putc(3C)设为线程安全时,宏的使用代价会突然变高。现在,宏至少需要两个内部子例程调用来锁定和解除锁定互斥。为避开此问题,提供了这些例程的替代版本getc_unlocked(3C)和putc_unlocked(3C)。getc_unlocked(3C)和putc_unlocked(3C)不会获得对互斥的锁定。这些宏的速度像原始非线程安全版本的getc(3C)和putc(3C)一样快。第5章•使用Solaris软件编程169 I/O问题但是,要采用线程安全方式使用这些宏,必须使用flockfile(3C)和funlockfile(3C)显式锁定和释放保护标准I/O流的互斥。对其中靠后例程的调用是在循环外进行的。对getc_unlocked()或putc_unlocked()的调用是在循环内进行的。170多线程编程指南•2006年10月 第66章安全和不安全的接口本章定义函数和库的MT安全级别。本章论述以下主题:■第171页中的“线程安全”■第173页中的“MT接口安全级别”■第175页中的“异步信号安全函数”■第175页中的“库的MT安全级别”线程安全线程安全可以避免数据竞争。不管数据值设置的正确与否,都会出现数据争用的情况,具体取决于多个线程访问和修改数据的顺序。不需要共享时,请为每个线程提供一个专用的数据副本。如果共享非常重要,则提供显式同步,以确保程序以确定的方式操作。当某个过程由多个线程同时执行时,如果该过程在逻辑上是正确的,则认为该过程是线程安全的。实际上,一般可分为以下几种安全性级别。■不安全■线程安全,可串行化■线程安全,MT安全通过将过程包含在语句中来锁定和解除锁定互斥,可以使不安全过程变成线程安全过程,而且可以进行串行化。示例6–1说明了fputs()的三个简化实现,最初线程是不安全的。下面是此例程的可串行化版本,它使用单一互斥来防止过程出现并发执行问题。实际上,单一互斥比通常需要的同步效果更强。当两个线程使用fputs()将输出发送到不同文件时,一个线程无需等待另一个线程。只有在共享输出文件时,线程才需要进行同步。171 线程安全最新版本是MT安全的。此版本对每个文件都使用一个锁定,允许两个线程同时指向不同的文件。因此,只要例程是线程安全的,该例程就是MT安全的,而且例程的执行不会对性能造成负面影响。示例6–1线程安全程度/*notthread-safe*/fputs(constchar*s,FILE*stream){char*p;for(p=s;*p;p++)putc((int)*p,stream);}/*serializable*/fputs(constchar*s,FILE*stream){staticmutex_tmut;char*p;mutex_lock(&m);for(p=s;*p;p++)putc((int)*p,stream);mutex_unlock(&m);}/*MT-Safe*/mutex_tm[NFILE];fputs(constchar*s,FILE*stream){172多线程编程指南•2006年10月 MT接口安全级别示例6–1线程安全程度(续)staticmutex_tmut;char*p;mutex_lock(&m[fileno(stream)]);for(p=s;*p;p++)putc((int)*p,stream);mutex_unlock(&m[fileno(stream)]0;}MT接口安全级别线程手册页man(3C)使用表6–1中列出的安全级别类别来描述接口对线程的支持程度。这些类别在Intro(3)手册页中进行了完整说明。表6–1接口安全级别类别说明安全可以从多线程应用程序中调用此代码安全(包含异常)有关异常的说明,请参见对应手册页的NOTES部分。不安全此接口与多线程应用程序结合使用时是不安全的,除非应用程序一次仅安排在库中执行一个线程。MT安全此接口已完全做好准备,可以执行多线程访问。此接口是安全的,而且支持一定的并发性。MT安全(包含异常)有关异常的列表,请参见《manpagessection3:BasicLibraryFunctions》中对应页中的NOTES部分。异步信号安全可以从信号处理程序中安全地调用此例程。执行异步信号安全例程的线程在被信号中断时,不会自行死锁。Fork1–安全每次调用Solarisfork1(2)或POSIXfork(2)时,此接口都会释放持有的锁定。有关库例程的安全级别,请参见参考手册页的第3部分。出于以下原因,特意未将某些函数设为安全的。■设置为MT安全的接口对单线程应用程序的性能有负面影响。第6章•安全和不安全的接口173 MT接口安全级别■库具有不安全的接口。例如,函数可能会返回指向栈中缓冲区的指针。可以对其中的某些函数使用可重复执行的对应函数。可重复执行的函数名称是在原始函数名称后附加"_r"。注意–确定名称不以"_r"结尾的函数是否是MT安全的唯一方法就是查看该函数的手册页。必须使用同步设备或通过限制初始线程来保护对标识为非MT安全的函数的使用。不安全接口的可重复执行函数对于包含不安全接口的大多数函数而言,存在例程的MT安全版本。新的MT安全例程的名称始终为原有不安全例程的名称附加"_r"后的形式。Solaris环境中提供表6–2"_r"例程。表6–2可重复执行函数asctime_r(3c)gethostbyname_r(3n)getservbyname_r(3n)ctermid_r(3s)gethostent_r(3n)getservbyport_r(3n)ctime_r(3c)getlogin_r(3c)getservent_r(3n)fgetgrent_r(3c)getnetbyaddr_r(3n)getspent_r(3c)fgetpwent_r(3c)getnetbyname_r(3n)getspnam_r(3c)fgetspent_r(3c)getnetent_r(3n)gmtime_r(3c)gamma_r(3m)getnetgrent_r(3n)lgamma_r(3m)getauclassent_r(3)getprotobyname_r(3n)localtime_r(3c)getauclassnam_r(3)getprotobynumber_r(3n)nis_sperror_r(3n)getauevent_r(3)getprotoent_r(3n)rand_r(3c)getauevnam_r(3)getpwent_r(3c)readdir_r(3c)getauevnum_r(3)getpwnam_r(3c)strtok_r(3c)getgrent_r(3c)getpwuid_r(3c)tmpnam_r(3s)getgrgid_r(3c)getrpcbyname_r(3n)ttyname_r(3c)getgrnam_r(3c)getrpcbynumber_r(3n)gethostbyaddr_r(3n)getrpcent_r(3n)174多线程编程指南•2006年10月 库的MT安全级别异步信号安全函数可以从信号处理程序中安全调用的函数就是异步信号安全函数。POSIX标准定义并列出了异步信号安全函数(IEEEStd1003.1-1990,3.3.1.3(3)(f),第55页)。除POSIX异步信号安全函数外,Solaris线程接口中的以下函数也是异步信号安全函数:■sema_post(3C)■thr_sigsetmask(3C),类似于pthread_sigmask(3C)■thr_kill(3C),类似于pthread_kill(3C)库的MT安全级别所有可能由线程从多线程程序中调用的例程都应该是MT安全的。因此,例程的两项或多项激活操作必须能够同时正确执行。这样,多线程程序使用的每个库接口都必须是MT安全级别。目前,并非所有库都是MT安全的。表6–3中列出了常用的MT安全库。其他的库最终会被修改为MT安全的。表6–3部分MT安全的库库注释lib/libc不安全的接口具有*_r形式的线程安全接口,通常包含不同的语义。lib/libdl_stubs支持静态切换编译lib/libintl国际化库lib/libm符合SystemVInterfaceDefinition,Edition3,X/OpenandANSIC的数学库lib/libmalloc空间有效内存分配库,请参见malloc(3X)lib/libmapmalloc基于mmap的备选内存分配库,请参见mapmalloc(3X)lib/libnslTLI接口、XDR、RPC客户机和服务器、netdir、netselect以及getXXbyYY接口都不是安全的,但都具有getXXbyYY_r形式的线程安全接口lib/libresolv线程特定errno支持lib/libsocket用于执行网络连接的套接字库lib/libw支持多字节语言环境的宽字符和宽字符串函数lib/straddr名称到地址的网络转换库第6章•安全和不安全的接口175 库的MT安全级别表6–3部分MT安全的库(续)库注释lib/libX11X11Windows库例程lib/libCC++运行时共享对象不安全库只有在单线程调用时,多进程程序才能安全地调用库中无法保证是MT安全级别的例程。176多线程编程指南•2006年10月 第77章编译和调试本章介绍如何编译和调试多线程程序。本章论述以下主题:■第177页中的“编译多线程应用程序”■第181页中的“调试多线程程序”编译多线程应用程序许多选项可用于头文件、定义标志和链接。为编译做准备编译和链接多线程程序时,需要以下项。Solaris软件应包括除C编译器以外的所有项。■标准C编译器■包括以下文件:■,,,■常规Solaris链接程序ln(1)■Solaris线程库(libthread)、POSIX线程库(libpthread),可能还有信号的POSIX实时库(librt)(仅限于Solaris9和以前的发行版)■MT安全库,如libc、libm、libw、libintl、libnsl、libsocket、libmalloc、libmapmalloc等177 编译多线程应用程序选择Solaris语义或POSIX语义某些函数在POSIX标准中的语义与在Solaris2.4发行版中的语义是不同的,Solaris2.4发行版基于早期的POSIX草案。函数定义是在编译时选择的。有关预期参数和返回值中差异的说明,请参见手册页section3:LibraryInterfacesandHeaders。以下是具有不同语义的函数:■asctime_r(3C)■ctime_r(3C)■ftrylockfile(3C)(新增)■getgrgid_r(3C)■getgrnam_r(3C)■getlogin_r(3C)■getpwnam_r(3C)■getpwuid_r(3C)■readdir_r(3C)■sigwait(2)■ttyname_r(3C)在Solaris9和以前的发行版中,Solarisfork(2)函数可以复制所有的线程fork-all行为。POSIXfork(2)函数仅复制调用线程fork-one行为,与Solarisfork1()函数是一样的。从Solaris10发行版开始,fork()的行为在未链接到-lpthreaad时可能会发生更改,以与POSIX版本保持一致。需要特别指出的是,fork()被重新定义为fork1()。因此,fork()将复制子进程中的调用线程。所有Solaris发行版中都支持fork1()的行为。新函数forkall()可以针对需要将父进程的所有线程复制到子进程中的应用程序提供此行为。包括包括文件可以编译与早期的Solaris软件发行版向上兼容的代码。此文件包含Solaris线程接口的声明。要使用POSIX线程调用thr_setconcurrency(3C),程序需要包括。包括文件(与-lpthread库结合使用)可以编译符合POSIX标准定义的多线程接口的代码。为了与POSIX完全符合,应该将功能测试宏_POSIX_C_SOURCE的值(long)设置为≥199506。请参见standards(5)手册页。对于1996版POSIX标准:cc89-D_POSIX_C_SOURCE=199506L[flags]file对于2001版POSIX标准:cc99-D_POSIX_C_SOURCE=200112L[flags]file...[-lrt]178多线程编程指南•2006年10月 编译多线程应用程序可以在同一个应用程序中混合使用Solaris线程与POSIX线程。请在应用程序中同时包括。如果二者混合使用,则当使用-D_REENTRANT编译时,将采用Solaris语义,而当使用-D_POSIX_C_SOURCE编译时,将采用POSIX语义。定义_REENTRANT或_POSIX_C_SOURCE对于POSIX行为,请使用-D_POSIX_C_SOURCE标志集≥199506L来编译应用程序。对于Solaris行为,请使用-D_REENTRANT标志来编译多线程程序。这些编译器标志适用于应用程序的每个模块。对于混合的应用程序,具有POSIX语义的Solaris线程使用-D_REENTRANT和-D_POSIX_PTHREAD_SEMANTICS标志进行编译。要编译单线程应用程序,请不要定义-D_REENTRANT标志,也不要定义-D_POSIX_C_SOURCE标志。不存在这些标志时,errno、stdio等的所有原有定义仍然生效。注–请在不使用-D_REENTRANT标志的条件下编译单线程应用程序。使用这种方式编译单线程应用程序,以避免将宏(如putc(3s))转换为可重复执行函数调用时引起的性能降低。总之,定义-D_POSIX_C_SOURCE的POSIX应用程序将获取例程的POSIX语义。仅定义-D_REENTRANT的应用程序将获取这些例程的Solaris语义。定义-D_POSIX_PTHREAD_SEMANTICS的Solaris应用程序将获取这些例程的POSIX语义,但仍然可以使用Solaris线程接口。同时定义-D_POSIX_C_SOURCE和-D_REENTRANT的应用程序将获取POSIX语义。使用libthread或libpthread链接对于POSIX线程行为(在Solaris9和以前的发行版中),请装入libpthread库。对于Solaris线程行为,请装入libthread库。有的POSIX程序员可能想使用-lthread进行链接,以保留fork()与fork1()之间的Solaris区别。-lpthread库使fork()的行为方式与Solarisfork1()调用的行为方式相同。在Solaris10和后续发行版中,两个线程库都不再是必需的,但是仍然可以为了实现兼容而指定库。所有的线程功能都已被移入标准C库中。要使用libthread,请在ld命令行的lc前面指定-lthread,或在cc命令行的末尾指定-lthread。要使用libthread,请在ld命令行的-lc前面指定-lthread,或在cc命令行的末尾指定-lthread。第7章•编译和调试179 编译多线程应用程序要使用libpthread,请在ld命令行的-lc前面指定-lpthread,或在cc命令行的末尾指定-lpthread。在Solaris9发行版之前,不应使用-lthread或-lpthread来链接非线程程序。这样做将在链接时建立在运行时启动的多线程机制。这些机制将使单线程应用程序的速度变慢,浪费系统资源,而且会在调试代码时产生误导性结果。在Solaris9和后续发行版中,使用-lthread或-lpthread链接非线程应用程序时不会为程序产生语义差异。也不会创建额外的线程或额外的LWP。只有主线程会像传统的单线程进程一样执行操作。对程序的唯一影响就是使系统库锁定成为实际锁定,与伪函数调用相反。您必须为获取无竞争锁定付出代价。在Solaris10发行版之前,如果应用程序没有链接-lthread或-lpthread,则对libthread和libpthread的所有调用都为空操作指令。运行时库libc具有许多预定义libthread和libpthread存根,这些存根都是空过程。当应用程序同时链接了libc和线程库时,将通过libthread或libpthread插入实际过程。注–对于使用线程的C++程序,请使用-mt选项(而不是-lthread)来编译和链接应用程序。-mt选项与libthread链接,并且能确保正确的库链接顺序。-lthread可能会导致程序进行核心转储。在POSIX环境中链接对于1996版POSIX标准,请使用以下选项来编译和链接应用程序:cc89-D_POSIX_C_SOURCE=199506L[flags]file...[-lrt]对于2001版POSIX标准,请使用以下选项来编译和链接应用程序:cc99-D_POSIX_C_SOURCE=200112L[flags]file...[-lrt]在Solaris环境中链接在Solaris线程环境中,请使用以下选项来编译和链接应用程序:cc-D_REENTRANT-DPOSIX_THREAD_SEMANTICS[flags]file...[-lrt]在混合环境中链接在混合环境中,请使用以下选项来编译和链接应用程序:cc-D_REENTRANT[flags]file...[-lrt]在混合使用时,需要包括thread.h和pthread.h。180多线程编程指南•2006年10月 调试多线程程序与POSIX信号的-lrt链接Solaris信号例程sema_*(3C)包含在标准的C库中。相对而言,您可以链接-lrt库,从而获取第124页中的“使用信号进行同步”中所述的标准sem_*(3R)POSIX信号例程。将原有模块与新模块链接表7–1说明,在将多线程对象模块与原有对象模块链接时应格外小心。表7–1使用或不使用_REENTRANT标志进行编译文件类型编译参考返回值原有对象文件(非线不使用_REENTRANT或静态存储传统的errno程)和新对象文件_POSIX_C_SOURCE标志新对象文件使用_REENTRANT或__errno,新的二进线程的errno定义地_POSIX_C_SOURCE标志制入口点址在libnsl中使用TLI的使用_REENTRANT或__t_errno,新的入口线程的t_errno定义程序要获取TLI全局错_POSIX_C_SOURCE标志(必点地址。误变量,请包括需)tiuser.h。备用线程库Solaris8发行版引入了备用的线程库实现,位于目录/usr/lib/lwp(32位)和/usr/lib/lwp/64(64位)中。在Solaris9发行版中,此实现成为标准的线程实现(位于/usr/lib和/usr/lib/64中)。从Solaris10发行版开始生效,所有的线程功能都已被移入libc中,不再需要任何单独的线程库。调试多线程程序下面论述的内容介绍了一些可能在多线程程序中导致错误的特征。多线程程序中常见的疏忽性问题以下列表指出了在多线程程序中可能导致错误的一些经常被疏忽的问题。■将指针作为新线程的参数传递给调用方栈。■在没有同步机制保护的情况下访问全局内存的共享可更改状态。第7章•编译和调试181 调试多线程程序■两个线程尝试轮流获取对同一对全局资源的权限时导致死锁。其中一个线程控制第一种资源,另一个线程控制第二种资源。其中一个线程放弃之前,任何一个线程都无法继续操作。■尝试重新获取已持有的锁(递归死锁)。■在同步保护中创建隐藏的间隔。如果受保护的代码段包含的函数释放了同步机制,而又在返回调用方之前重新获取了该同步机制,则将在保护中出现此间隔。结果具有误导性。对于调用方,表面上看全局数据已受到保护,而实际上未受到保护。■将UNIX信号与线程混合时,使用sigwait(2)模型来处理异步信号。■调用setjmp(3C)和longjmp(3C),然后长时间跳跃,而不释放互斥锁。■从对*_cond_wait()或*_cond_timedwait()的调用中返回后无法重新评估条件。■忘记已创建缺省线程PTHREAD_CREATE_JOINABLE并且必须使用pthread_join(3C)来进行回收。请注意,pthread_exit(3C)不会释放其存储空间。■执行深入嵌套、递归调用以及使用大型自动数组可能会导致问题,因为多线程程序与单线程程序相比对栈大小的限制更多。■指定不适当的栈大小,或使用非缺省栈。多线程程序(特别是那些包含错误的程序)经常在两次连续运行中的行为方式不同,即使输入相同也是如此。此行为是由线程调度顺序的差异所导致的。一般情况下,多线程错误是统计得出的,不具有确定性。通常,与基于断点的调试相比,跟踪是用于查找执行顺序问题的一种更有效的方法。使用TNF实用程序跟踪和调试请使用TNF实用程序跟踪、调试和收集应用程序和库中的性能分析信息。TNF实用程序将内核以及多个用户进程和线程中的跟踪信息整合在一起。TNF实用程序对于多线程代码特别有用。TNF实用程序包括在Solaris软件中,是该软件的一部分。使用TNF实用程序,可以轻松跟踪和调试多线程程序。有关使用prex(1)和tnfdump(1)的详细信息,请参见TNF手册页。使用truss有关跟踪系统调用、信号和用户级别函数调用的信息,请参见truss(1)手册页。使用mdb有关mdb的信息,请参见《SolarisModularDebuggerGuide》。可以使用下面的mdb命令来访问多线程程序的LWP。182多线程编程指南•2006年10月 调试多线程程序$l如果目标为用户进程,则将列显有代表性的线程的LWPID。$L如果目标为用户进程,则将列显目标中每个LWP的LWPID。pid::attach附加到编号为pid的进程。::release释放以前附加的进程或核心转储文件。随后可以由prun(1)继续处理进程,或者可通过应用MDB或其他调试器来恢复进程。address::context上下文切换到指定进程。这些用于设置条件断点的命令通常很有用。[addr]::bp[+/-dDestT][-ccmd][-ncount]sym...在指定的位置设置断点。addr::delete[id|all]删除包含给定ID编号的事件说明符。使用dbx使用dbx实用程序,可以调试和执行使用C++、ANSIC和FORTRAN编写的源代码程序。dbx与调试器接受同样的命令,但使用标准的终端(TTY)接口。dbx和调试器都支持调试多线程程序。有关如何启动dbx的说明,请参见dbx(1)手册页。有关dbx的概述,请参见《DebuggingaProgramWithdbx》。调试器功能在dbx的调试器GUI的联机帮助中介绍。表7–2中列出的所有dbx选项均可支持多线程应用程序。表7–2MT程序的dbx选项选项操作contatline[-sigsignoid]在包含信号signo的line中继续执行操作。id(如果存在)指定哪个线程或LWP继续操作。缺省值为all。lwp显示当前的LWP。切换到给定LWP[lwpid]。lwps列出当前进程中所有的LWP。next...tid单步执行给定线程。跳过函数调用时,所有的LWP都会在该函数调用期间隐式恢复。不能单步执行非活动线程。next...lid单步执行给定LWP。跳过函数时不会隐式恢复所有LWP。所含的给定线程处于活动状态的LWP。跳过函数时不会隐式恢复所有LWP。step...tid单步执行给定线程。跳过函数调用时,所有的LWP都会在该函数调用期间隐式恢复。不能单步执行非活动线程。第7章•编译和调试183 调试多线程程序表7–2MT程序的dbx选项(续)选项操作step...lid单步执行给定LWP。跳过函数时不会隐式恢复所有LWP。stepi...lid给定的LWP。stepi...tid所含的给定线程处于活动状态的LWP。thread显示当前线程。切换到线程tid。在下面所有的变体中,可选的tid表示当前线程。thread-info[tid]列显有关给定线程的所有已知信息。thread-blocks[tid]列显阻塞其他线程的给定线程持有的所有锁定。thread-suspend[tid]使给定线程进入暂停状态。thread-resume[tid]取消暂停给定线程。thread-hide[tid]隐藏给定线程或当前线程。该线程不会出现在通用的threads列表中。thread-unhide[tid]取消隐藏给定线程或当前线程。thread-unhideall取消隐藏所有线程。threads列显所有已知线程的列表。threads-all列显通常不会列显的线程(僵线程)。threads-modeall|filter控制在缺省情况下,threads是列显所有线程,还是过滤线程。threads-modeauto|manual实现线程列表的自动更新。threads-mode回显当前模式。线程或LWPID可以按照以前的任何形式来追溯指定实体。184多线程编程指南•2006年10月 第88章Solaris线程编程本章比较了Solaris线程和POSIX线程的应用程序编程接口(applicationprogramminginterface,API),并介绍了POSIX线程中没有的Solaris功能。本章讨论以下主题:■第185页中的“比较Solaris线程和POSIX线程的API”■第189页中的“Solaris线程的独有函数”■第192页中的“相似的同步函数-读写锁”■第199页中的“相似的Solaris线程函数”■第210页中的“相似的同步函数-互斥锁”■第215页中的“相似的同步函数:条件变量”■第220页中的“相似的同步函数:信号”■第227页中的“fork()和Solaris线程的特殊问题”比较Solaris线程和POSIX线程的APISolaris线程API和pthreadAPI是同一问题的两种不同解决方案,即在应用程序软件中建立并行性。尽管每个API都是完整的,但是可以安全地在同一程序中混合使用Solaris线程函数和pthread函数。不过,这两个API并不完全匹配。Solaris线程支持pthread中没有的函数,而pthread中则包括Solaris接口不支持的函数。对于那些匹配的函数,尽管信息内容实际相同,但是关联参数可能并不相同。通过合并这两个API,可以使用仅存在于其中一个API中的功能来增强另一个API。同样,在同一个系统中还可以同时运行仅使用Solaris线程的应用程序和仅使用pthread的应用程序。API的主要差异Solaris线程和pthread在API操作和语法方面非常相似。表8–1中列出了两者之间的主要差异。185 比较Solaris线程和POSIX线程的API表8–1Solaris线程和pthread的独有功能Solaris线程POSIX线程使用thr_前缀表示线程函数的名称,使用使用pthread_前缀表示pthread函数的名称,使用sem_sema_前缀表示信号函数的名称前缀表示信号函数的名称能够创建“守护进程”线程取消语义暂停和继续执行线程调度策略函数比较表下表对Solaris线程函数和pthread函数进行了比较。请注意,即使Solaris线程函数和pthread函数看上去完全相同,但是其参数可以不同。如果某个进行比较的接口在pthread或Solaris线程中不可用,则会在相应的列中显示一个连字符"-"。pthread列中后跟"POSIX.1b"的各项属于POSIX实时标准规范,而不属于pthread。表8–2Solaris线程和POSIXpthread的比较Solaris线程pthreadthr_create()pthread_create()thr_exit()pthread_exit()thr_join()pthread_join()thr_yield()sched_yield()POSIX.1bthr_self()pthread_self()thr_kill()pthread_kill()thr_sigsetmask()pthread_sigmask()thr_setprio()pthread_setschedparam()thr_getprio()pthread_getschedparam()thr_setconcurrency()pthread_setconcurrency()thr_getconcurrency()pthread_getconcurrency()thr_suspend()-thr_continue()-thr_keycreate()pthread_key_create()186多线程编程指南•2006年10月 比较Solaris线程和POSIX线程的API表8–2Solaris线程和POSIXpthread的比较(续)Solaris线程pthread-pthread_key_delete()thr_setspecific()pthread_setspecific()thr_getspecific()pthread_getspecific()-pthread_once()-pthread_equal()-pthread_cancel()-pthread_testcancel()-pthread_cleanup_push()-pthread_cleanup_pop()-pthread_setcanceltype()-pthread_setcancelstate()mutex_lock()pthread_mutex_lock()mutex_unlock()pthread_mutex_unlock()mutex_trylock()pthread_mutex_trylock()mutex_init()pthread_mutex_init()mutex_destroy()pthread_mutex_destroy()cond_wait()pthread_cond_wait()cond_timedwait()pthread_cond_timedwait()cond_reltimedwait()pthread_cond_reltimedwait_np()cond_signal()pthread_cond_signal()cond_broadcast()pthread_cond_broadcast()cond_init()pthread_cond_init()cond_destroy()pthread_cond_destroy()rwlock_init()pthread_rwlock_init()rwlock_destroy()pthread_rwlock_destroy()rw_rdlock()pthread_rwlock_rdlock()rw_wrlock()pthread_rwlock_wrlock()rw_unlock()pthread_rwlock_unlock()第8章•Solaris线程编程187 比较Solaris线程和POSIX线程的API表8–2Solaris线程和POSIXpthread的比较(续)Solaris线程pthreadrw_tryrdlock()pthread_rwlock_tryrdlock()rw_trywrlock()pthread_rwlock_trywrlock()-pthread_rwlockattr_init()-pthread_rwlockattr_destroy()-pthread_rwlockattr_getpshared()-pthread_rwlockattr_setpshared()sema_init()sem_init()POSIX.1bsema_destroy()sem_destroy()POSIX.1bsema_wait()sem_wait()POSIX.1bsema_post()sem_post()POSIX.1bsema_trywait()sem_trywait()POSIX.1bfork1()fork()-pthread_atfork()forkall(),多线程副本--pthread_mutexattr_init()-pthread_mutexattr_destroy()mutex_init()中的type参数pthread_mutexattr_setpshared()-pthread_mutexattr_getpshared()-pthread_mutex_attr_settype()-pthread_mutex_attr_gettype()-pthread_condattr_init()-pthread_condattr_destroy()cond_init()中的type参数pthread_condattr_setpshared()-pthread_condattr_getpshared()-pthread_attr_init()-pthread_attr_destroy()thr_create()中的THR_BOUND标志pthread_attr_setscope()-pthread_attr_getscope()188多线程编程指南•2006年10月 Solaris线程的独有函数表8–2Solaris线程和POSIXpthread的比较(续)Solaris线程pthread-pthread_attr_setguardsize()-pthread_attr_getguardsize()thr_create()中的stack_size参数pthread_attr_setstacksize()-pthread_attr_getstacksize()thr_create()中的stack_addr参数pthread_attr_setstack()-pthread_attr_getstack()thr_create()中的THR_DETACH标志pthread_attr_setdetachstate()-pthread_attr_getdetachstate()-pthread_attr_setschedparam()-pthread_attr_getschedparam()-pthread_attr_setinheritsched()-pthread_attr_getinheritsched()-pthread_attr_setsschedpolicy()-pthread_attr_getschedpolicy()要使用本章中介绍的用于Solaris9和以前发行版的Solaris线程函数,必须使用Solaris线程库-lthread进行链接。对于Solaris线程和pthread来说,即使函数名或参数可能会有所不同,但是操作实际上是相同的。此处仅提供了一个简单的示例,其中包括正确的头文件和函数原型。如果没有为Solaris线程函数提供返回值,请参见《manpagessection3:BasicLibraryFunctions》中的相应页以获取函数的返回值。有关Solaris相关函数的更多信息,请参见相关pthread文档中以类似方式命名的函数。如果Solaris线程函数所提供的功能在pthread中不可用,则会提供这些函数的完整说明。Solaris线程的独有函数本节介绍Solaris线程的独有函数:用于暂停执行线程和继续执行暂停的线程。第8章•Solaris线程编程189 Solaris线程的独有函数暂停执行线程thr_suspend(3C)可用来立即暂停执行target_thread所指定的线程。如果从thr_suspend()成功返回,则将不再执行暂停的线程。因为thr_suspend()在暂停目标线程时不会考虑该线程可能持有的锁,所以,在使用thr_suspend()时一定要格外小心。如果要暂停的线程调用的函数需要由已暂停的目标线程拥有的锁,则将产生死锁。thr_suspend语法#includeintthr_suspend(thread_ttid);线程暂停之后,以后调用thr_suspend()将不起任何作用。信号无法唤醒暂停的线程。线程恢复执行之前,信号将一直保持暂挂状态。在以下概要中,pthread中定义的pthread_ttid与Solaris线程中定义的thread_ttid相同。这两个tid值可以通过赋值或通过使用强制转换来互换使用。thread_ttid;/*tidfromthr_create()*//*pthreadsequivalentofSolaristidfromthreadcreated*//*withpthread_create()*/pthread_tptid;intret;ret=thr_suspend(tid);/*usingpthreadsIDvariablewithacast*/ret=thr_suspend((thread_t)ptid);190多线程编程指南•2006年10月 Solaris线程的独有函数thr_suspend返回值thr_suspend()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,thr_suspend()将失败并返回对应的值。ESRCH描述:当前的进程中找不到tid。继续执行暂停的线程thr_continue(3C)可用来恢复执行暂停的线程。继续执行暂停的线程之后,以后调用thr_continue()将不起任何作用。thr_continue语法#includeintthr_continue(thread_ttid);信号无法唤醒暂停的线程。thr_continue()继续执行暂停的线程之前,信号将一直保持暂挂状态。在pthread中定义的pthread_ttid与在Solaris线程中定义的thread_ttid相同。这两个tid值可以通过赋值或通过使用强制转换来互换使用。thread_ttid;/*tidfromthr_create()*//*pthreadsequivalentofSolaristidfromthreadcreated*//*withpthread_create()*/pthread_tptid;intret;ret=thr_continue(tid);第8章•Solaris线程编程191 相似的同步函数-读写锁/*usingpthreadsIDvariablewithacast*/ret=thr_continue((thread_t)ptid)thr_continue返回值thr_continue()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,thr_continue()将失败并返回对应的值。ESRCH描述:当前的进程中找不到tid。相似的同步函数-读写锁读写锁允许多个线程同时进行读取访问,但限制每次只能对一个线程进行写入访问。本节讨论了以下主题:■初始化读取器/写入器锁■获取读锁■尝试获取读锁■获取写锁■尝试获取写锁■解除锁定读取器/写入器锁■销毁读取器/写入器锁的状态一个线程持有读锁时,其他线程也可以获取读锁,但要获取写锁则必须等待。如果一个线程持有写锁或者正在等待获取写锁,则其他线程必须等待才能获取读锁或写锁。相比互斥锁,读写锁速度较慢。但是,如果许多并发线程读取使用锁保护的数据,但不经常进行写入,则使用读写锁可以提高性能。使用读写锁可以同步此进程和其他进程中的线程。读写锁是在可以写入并在协作进程之间共享的内存中分配的。有关针对此行为映射读写锁的信息,请参见mmap(2)手册页。缺省情况下,多个线程正在等待读写锁时,获取锁的顺序是不确定的。但是,为了避免写入器资源匮乏,对于优先级相同的写入器和读取器,Solaris线程软件包中写入器往往优先于读取器。读写锁在使用之前必须先初始化。初始化读写锁使用rwlock_init(3C)可以初始化rwlp所指向的读写锁并将锁的状态设置为未锁定。192多线程编程指南•2006年10月 相似的同步函数-读写锁rwlock_init语法#include(或#include)intrwlock_init(rwlock_t*rwlp,inttype,void*arg);type可以是以下值之一:■USYNC_PROCESS。读写锁可用来同步此进程和其他进程中的线程。arg会被忽略。■USYNC_THREAD。读写锁可用来仅同步此进程中的线程。arg会被忽略。同一个读写锁不能同时由多个线程初始化。读写锁还可以通过在清零的内存中进行分配来初始化,在这种情况下假设type为USYNC_THREAD。对于其他线程可能正在使用的读写锁,不得重新初始化。对于POSIX线程,请参见第137页中的“pthread_rwlock_init语法”。初始化进程内读写锁#includerwlock_trwlp;intret;/*tobeusedwithinthisprocessonly*/ret=rwlock_init(&rwlp,USYNC_THREAD,0);初始化进程间读写锁#includerwlock_trwlp;intret;/*tobeusedamongallprocesses*/ret=rwlock_init(&rwlp,USYNC_PROCESS,0);第8章•Solaris线程编程193 相似的同步函数-读写锁rwlock_init返回值rwlock_init()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:参数无效。EFAULT描述:rwlp或arg指向的地址非法。获取读锁使用rw_rdlock(3C)可以获取rwlp所指向的读写锁中的读锁。rw_rdlock语法#include(或#include)intrw_rdlock(rwlock_t*rwlp);如果读写锁中的写锁已经锁定,则调用线程将阻塞,直到释放写锁为止。否则,将获取读锁。对于POSIX线程,请参见第138页中的“pthread_rwlock_rdlock语法”。rw_rdlock返回值rw_rdlock()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:参数无效。EFAULT描述:rwlp指向的地址非法。尝试获取读锁使用rw_tryrdlock(3C)可以尝试获取rwlp所指向的读写锁中的读锁。194多线程编程指南•2006年10月 相似的同步函数-读写锁rw_tryrdlock语法#include(或#include)intrw_tryrdlock(rwlock_t*rwlp);如果读写锁中的写锁已经锁定,则rw_tryrdlock()将返回错误。否则,将获取读锁。对于POSIX线程,请参见第139页中的“pthread_rwlock_tryrdlock语法”。rw_tryrdlock返回值rw_tryrdlock()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:参数无效。EFAULT描述:rwlp指向的地址非法。EBUSY描述:rwlp所指向的读写锁已经锁定。获取写锁使用rw_wrlock(3C)可以获取rwlp所指向的读写锁中的写锁。rw_wrlock语法#include(或#include)intrw_wrlock(rwlock_t*rwlp);如果读写锁中的读锁或写锁已经锁定,则调用线程将阻塞,直到释放所有的读锁和写锁为止。读写锁中的写锁一次只能由一个线程持有。对于POSIX线程,请参见第139页中的“pthread_rwlock_wrlock语法”。rw_wrlock返回值rw_wrlock()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。第8章•Solaris线程编程195 相似的同步函数-读写锁EINVAL描述:参数无效。EFAULT描述:rwlp指向的地址非法。尝试获取写锁使用rw_trywrlock(3C)可以尝试获取rwlp所指向的读写锁中的写锁。rw_trywrlock语法#include(或#include)intrw_trywrlock(rwlock_t*rwlp);如果读写锁上的读锁或写锁已经锁定,则rw_trywrlock()将返回错误。对于POSIX线程,请参见第140页中的“pthread_rwlock_trywrlock语法”。rw_trywrlock返回值rw_trywrlock()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:参数无效。EFAULT描述:rwlp指向的地址非法。EBUSY描述:rwlp所指向的读写锁已经锁定。解除锁定读写锁使用rw_unlock(3C)可以解除锁定rwlp所指向的读写锁。rw_unlock语法#include(或#include)196多线程编程指南•2006年10月 相似的同步函数-读写锁intrw_unlock(rwlock_t*rwlp);读写锁必须处于锁定状态,并且调用线程必须持有读锁或写锁。如果还有其他线程正在等待读写锁成为可用,则其中一个线程将被解除阻塞。对于POSIX线程,请参见第140页中的“pthread_rwlock_unlock语法”。rw_unlock返回值rw_unlock()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。EINVAL描述:参数无效。EFAULT描述:rwlp指向的地址非法。销毁读写锁的状态使用rwlock_destroy(3C)可以销毁与rlwp所指向的读写锁相关联的任何状态。rwlock_destroy语法#include(或#include)intrwlock_destroy(rwlock_t*rwlp);用来存储读写锁的空间不会释放。对于POSIX线程,请参见第141页中的“pthread_rwlock_destroy语法”。示例8–1使用银行帐户来说明读写锁。尽管该程序可能会允许多个线程对帐户余额进行并行只读访问,但是仅允许使用一个写入器。请注意,get_balance()函数需要使用该锁才能确保以原子方式添加支票帐户余额和储蓄帐户余额。示例8–1读写银行帐户rwlock_taccount_lock;floatchecking_balance=100.0;floatsaving_balance=100.0;...第8章•Solaris线程编程197 相似的同步函数-读写锁示例8–1读写银行帐户(续)rwlock_init(&account_lock,0,NULL);...floatget_balance(){floatbal;rw_rdlock(&account_lock);bal=checking_balance+saving_balance;rw_unlock(&account_lock);return(bal);}voidtransfer_checking_to_savings(floatamount){rw_wrlock(&account_lock);checking_balance=checking_balance-amount;saving_balance=saving_balance+amount;rw_unlock(&account_lock);}rwlock_destroy返回值rwlock_destroy()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。198多线程编程指南•2006年10月 相似的Solaris线程函数EINVAL描述:参数无效。EFAULT描述:rwlp指向的地址非法。相似的Solaris线程函数表8–3相似的Solaris线程函数操作相关函数说明创建线程第199页中的“thr_create语法”获取最小的栈大小第201页中的“thr_min_stack语法”获取线程标识符第202页中的“thr_self语法”停止执行线程第203页中的“thr_yield语法”向线程发送信号第203页中的“thr_kill语法”访问调用线程的信号掩码第203页中的“thr_sigsetmask语法”终止线程第204页中的“thr_exit语法”等待线程终止第205页中的“thr_join语法”创建线程特定的数据键第207页中的“thr_keycreate语法”设置线程特定数据第207页中的“thr_setspecific语法”获取线程特定数据第208页中的“thr_getspecific语法”设置线程优先级第209页中的“thr_setprio语法”获取线程优先级第210页中的“thr_getprio语法”创建线程thr_create(3C)例程是Solaris线程接口中最详细的所有例程其中之一。使用thr_create(3C)可以向当前的进程中增加新的受控线程。对于POSIX线程,请参见第23页中的“pthread_create语法”。thr_create语法#include第8章•Solaris线程编程199 相似的Solaris线程函数intthr_create(void*stack_base,size_tstack_size,void*(*start_routine)(void*),void*arg,longflags,thread_t*new_thread);size_tthr_min_stack(void);请注意,新线程不会继承暂挂的信号,但确实会继承优先级和信号掩码。stack_base。包含新线程所使用的栈的地址。如果stack_base为NULL,则thr_create()会为新线程分配一个至少为stack_size字节的栈。stack_size。包含新线程所使用的栈的大小(以字节数表示)。如果stack_size为零,则使用缺省大小。在大多数情况下,零值最适合。如果stack_size不为零,则stack_size必须大于thr_min_stack()返回的值。通常,无需为线程分配栈空间。系统会为每个线程的没有保留交换空间的栈分配1MB的虚拟内存。系统使用mmap(2)的-MAP_NORESERVE选项来进行分配。start_routine。包含用以开始执行新线程的函数。start_routine()返回时,该线程将退出,并且其退出状态会设置为start_routine返回的值。请参见第204页中的“thr_exit语法”。arg。可以是void所描述的任何变量,通常是任何大小为4字节的值。对于较大的值,必须通过将该参数指向对应的变量来间接传递。请注意,仅可以提供一个参数。要在程序中采用多个参数,请将多个参数编码为单个参数,如通过将这些参数放在一个结构中。flags。指定所创建的线程的属性。在大多数情况下,最适合使用零值。flags中的值是通过对以下参数执行按位或运算(包含边界值)来构造的:■THR_SUSPENDED。暂停新线程,并且不执行start_routine,直到thr_continue()启动该线程为止。使用THR_SUSPENDED可以在运行该线程之前对其执行操作,如更改其优先级。■THR_DETACHED。分离新线程,以便在该线程终止之后,可以立即重用其线程ID和其他资源。如果不想等待线程终止,可以设置THR_DETACHED。注–如果没有明确分配同步,则未暂停的分离线程会失败。如果失败,则从thr_create()返回该线程的创建者之前,会将该线程ID重新指定给另一个新线程。■THR_BOUND。将新线程永久绑定到LWP。新线程是绑定线程。从Solaris9发行版开始,系统不再区分绑定线程和非绑定线程,所有的线程均视为绑定线程。200多线程编程指南•2006年10月 相似的Solaris线程函数■THR_DAEMON。将新线程标记为守护进程。守护进程线程始终处于分离状态。THR_DAEMON表示THR_DETACHED。所有非守护进程线程退出时,该进程也会随之退出。守护进程线程不会影响进程的退出状态,并且在退出对线程数进行计数时会被忽略。进程的退出方法有两种,一是调用exit(),二是使用进程中不是通过THR_DAEMON标志创建的每个线程来调用thr_exit(3C)。进程所调用的应用程序或库可以创建一个或多个线程,在确定是否退出时应当忽略(不计数)这些线程。THR_DAEMON标志可标识在进程退出条件中不计数的线程。new_thread。如果new_thread不为NULL,则它将指向thr_create()成功时用来存储新线程ID的位置。调用方负责提供该参数所指向的存储空间。线程ID仅在调用进程中有效。如果对该标识符不感兴趣,请向new_thread提供一个NULL值。thr_create返回值thr_create()函数在成功完成之后返回零。其他任何返回值都表示出现了错误。如果检测到以下情况之一,thr_create()将失败并返回对应的值。EAGAIN描述:超出了系统限制,如创建的LWP过多。ENOMEM描述:可用内存不足,无法创建新线程。EINVAL描述:stack_base不为NULL,并且stack_size小于thr_min_stack()返回的值。获取最小栈大小使用thr_min_stack(3C)可以获取线程的最小栈大小。Solaris线程中的栈行为通常与pthread中的栈行为相同。有关设置和操作栈的更多信息,请参见第69页中的“关于栈”。thr_min_stack语法#includesize_tthr_min_stack(void);thr_min_stack()会返回执行空线程所需的空间量。创建空线程的目的是执行空过程。由于有用线程需要的栈要大于绝对最小栈,因此在减小栈大小时请务必小心。第8章•Solaris线程编程201 相似的Solaris线程函数执行非空过程的线程所分配的栈大小应大于thr_min_stack()的大小。如果某个线程是借助于用户提供的栈创建的,则用户必须保留足够的空间才能运行该线程。动态链接的执行环境会增加确定线程最小栈要求的难度。可以通过两种方法来指定自定义栈。第一种方法是为栈位置提供NULL,从而要求运行时库为该栈分配空间,但是向thr_create()提供stacksize参数中所需的大小。另一种方法是全面负责栈管理的各个方面,并向thr_create()提供一个指向该栈的指针。这意味着不但需要负责分配栈,还需要负责取消分配栈。线程终止时,必须安排对该线程的栈进行处理。当您分配自己的栈时,请确保通过调用mprotect(2)在该栈末尾附加一个红色区域。大多数用户都不应当通过用户提供的栈来创建线程。用户提供的栈之所以存在,只是为了支持要求对其执行环境进行完全控制的应用程序。相反,用户应当由系统来管理对栈的分配。系统提供的缺省栈应当能够满足所创建的任何线程的要求。thr_min_stack返回值未定义任何错误。获取线程标识符使用thr_self(3C)可以获取调用线程的ID。对于POSIX线程,请参见第36页中的“pthread_self语法”。thr_self语法#includethread_tthr_self(void);thr_self返回值未定义任何错误。停止执行线程thr_yield(3C)会导致当前的线程停止执行,以便执行另一个具有相同或更高优先级的线程。否则,thr_yield()不起任何作用。但是,调用thr_yield()无法保证当前的线程会停止执行。202多线程编程指南•2006年10月 相似的Solaris线程函数thr_yield语法#includevoidthr_yield(void);thr_yield返回值thr_yield()不会返回任何内容并且不会设置errno。向线程发送信号thr_kill(3C)可用来向线程发送信号。对于POSIX线程,请参见第36页中的“pthread_self语法”。thr_kill语法#include#includeintthr_kill(thread_ttarget_thread,intsig);thr_kill返回值如果成功完成,thr_kill()将返回0。如果检测到以下任一情况,thr_kill()将失败并返回对应的值。如果失败,则不发送任何信号。ESRCH描述:未找到与threadID所指定的线程相关联的线程。EINVAL描述:sig参数值不为零。sig无效或者是不支持的信号编号。访问调用线程的信号掩码使用thr_sigsetmask(3C)可以更改或检查调用线程的信号掩码。thr_sigsetmask语法#include#include第8章•Solaris线程编程203 相似的Solaris线程函数intthr_sigsetmask(inthow,constsigset_t*set,sigset_t*oset);thr_sigsetmask()可用来更改或检查调用线程的信号掩码。每个线程都有自已的信号掩码。新线程会继承调用线程的信号掩码和优先级,但不会继承暂挂的信号。新线程的暂挂信号将为空。如果参数set的值不为NULL,则set会指向一组信号,这组信号可用来修改当前阻塞的信号组。如果set的值为NULL,则how的值没有意义,并且不会修改线程的信号掩码。使用此行为可了解有关当前阻塞的信号的情况。how的值可指定更改信号组的方法。how可以为以下值之一。■SIG_BLOCK。set对应于一组要阻塞的信号。这些信号会添加到当前的信号掩码中。■SIG_UNBLOCK。set对应于一组要解除阻塞的信号。这些信号会从当前的信号掩码中删除。■SIG_SETMASK。set对应于新的信号掩码。当前的信号掩码将替换为set。thr_sigsetmask返回值如果成功完成,thr_sigsetmask()将返回0。如果检测到以下任一情况,thr_sigsetmask()将失败并返回对应的值。EINVAL描述:set不为NULL,并且how的值未定义。终止线程使用thr_exit(3C)可以终止线程。对于POSIX线程,请参见第43页中的“pthread_exit语法”。thr_exit语法#includevoidthr_exit(void*status);thr_exit返回值thr_exit()不返回到其调用方。等待线程终止使用thr_join(3C)可以等待目标线程终止。对于POSIX线程,请参见第25页中的“pthread_join语法”。204多线程编程指南•2006年10月 相似的Solaris线程函数thr_join语法#includeintthr_join(thread_ttid,thread_t*departedid,void**status);目标线程必须是当前进程的成员,而不能是分离线程或守护进程线程。多个线程不能等待同一个线程完成,否则仅有一个线程会成功完成。其他线程将终止,并返回ESRCH错误。如果目标线程已经终止,thr_join()将不会阻塞对调用线程的处理。thr_join,加入特定线程#includethread_ttid;thread_tdepartedid;intret;void*status;/*waitingtojointhread"tid"withstatus*/ret=thr_join(tid,&departedid,&status);/*waitingtojointhread"tid"withoutstatus*/ret=thr_join(tid,&departedid,NULL);/*waitingtojointhread"tid"withoutreturnidandstatus*/ret=thr_join(tid,NULL,NULL);如果tid为(thread_t)0,则thread_join()将等待进程中的任何非分离线程终止。换句话说,如果未指定线程标识符,则任何未分离的线程退出都将导致返回thread_join()。第8章•Solaris线程编程205 相似的Solaris线程函数thr_join,加入任何线程#includethread_ttid;thread_tdepartedid;intret;void*status;/*waitingtojoinanynon-detachedthreadwithstatus*/ret=thr_join(0,&departedid,&status);通过在Solaristhr_join()中使用0来表示线程ID,进程中的任何非分离线程退出时都将执行加入操作。departedid表示现有线程的线程ID。thr_join返回值thr_join()在成功运行后返回0。如果检测到以下任一情况,thr_join()将失败并返回对应的值。ESRCH描述:未找到与目标线程ID对应的非分离线程。EDEADLK描述:检测到死锁,或者目标线程的值指定了调用线程。创建线程特定的数据键使用thr_keycreate(3C)可分配键,用于标识进程中线程特定数据。键可全局应用于进程中的所有线程。创建键时,每个线程都会将一个值与其绑定。除了函数的名称和参数以外,Solaris线程的线程特定数据与POSIX线程的线程特定数据完全相同。本节概述了Solaris函数。对于POSIX线程,请参见第29页中的“pthread_key_create语法”。206多线程编程指南•2006年10月 相似的Solaris线程函数thr_keycreate语法#includeintthr_keycreate(thread_key_t*keyp,void(*destructor)(void*value));keyp为每个绑定线程单独维护特定的值。所有的线程最初都会绑定到专用元素keyp,该元素可用于访问其线程特定数据。创建键时,对于所有活动线程,将为新键赋予值NULL。此外在创建线程时,还会为以前在新线程中创建的所有键赋予值NULL。destructor函数是可选的,可以将其与每个keyp相关联。线程退出时,如果keyp具有非NULL的destructor,并且线程具有与keyp相关联的非NULLvalue,则destructor将用当前的关联value进行调用。如果线程退出时存在多个destructor与其相关,则destructor的调用顺序是不确定的。thr_keycreate返回值thr_keycreate()在成功运行后返回0。如果检测到以下任一情况,thr_keycreate()将失败并返回对应的值。EAGAIN描述:系统资源不足,无法创建另一个线程特定的数据键,或者键数目超过了PTHREAD_KEYS_MAX的每进程限制。ENOMEM描述:可用内存不足,无法将value与keyp相关联。设置线程特定的数据值thr_setspecific(3C)可用来将value绑定到线程特定的数据键(对于调用线程来说为key)。对于POSIX线程,请参见第31页中的“pthread_setspecific语法”。thr_setspecific语法#includeintthr_setspecific(thread_key_tkey,void*value);第8章•Solaris线程编程207 相似的Solaris线程函数thr_setspecific返回值thr_setspecific()在成功运行后返回0。如果检测到以下任一情况,thr_setspecific()将失败并返回对应的值。ENOMEM描述:可用内存不足,无法将value与keyp相关联。EINVAL描述:keyp无效。获取线程特定的数据值thr_getspecific(3C)可用来将当前绑定到调用线程的key的值存储到valuep所指向的位置。对于POSIX线程,请参见第32页中的“pthread_getspecific语法”。thr_getspecific语法#includeintthr_getspecific(thread_key_tkey,void**valuep);thr_getspecific返回值thr_getspecific()在成功运行后返回0。如果检测到以下任一情况,thr_getspecific()将失败并返回对应的值。ENOMEM描述:可用内存不足,无法将value与keyp相关联。EINVAL描述:keyp无效。设置线程的优先级在Solaris线程中,对于在创建时与父线程具有不同优先级的线程,可以在SUSPEND模式下创建。在暂停之后,可以通过使用thr_setprio(3C)函数调用来修改线程的优先级。thr_setprio()完成之后,线程可恢复执行。争用同步对象时,高优先级的线程优先于低优先级的线程。208多线程编程指南•2006年10月 相似的Solaris线程函数thr_setprio语法thr_setprio(3C)可用来将当前进程内tid所指定线程的优先级更改为newprio所指定的优先级。对于POSIX线程,请参见第38页中的“pthread_setschedparam语法”。#includeintthr_setprio(thread_ttid,intnewprio)缺省情况下,线程是基于范围从0(最不重要)到127(最重要)的固定优先级来调度的。thread_ttid;intret;intnewprio=20;/*suspendedthreadcreation*/ret=thr_create(NULL,NULL,func,arg,THR_SUSPENDED,&tid);/*setthenewpriorityofsuspendedchildthread*/ret=thr_setprio(tid,newprio);/*suspendedchildthreadstartsexecutingwithnewpriority*/ret=thr_continue(tid);thr_setprio返回值thr_setprio()在成功运行后返回0。如果检测到以下任一情况,thr_setprio()将失败并返回对应的值。ESRCH描述:tid指定的值不引用现有的线程。EINVAL描述:priority的值对于与tid相关联的调度类没有意义。第8章•Solaris线程编程209 相似的同步函数-互斥锁获取线程的优先级使用thr_getprio(3C)可以获取线程的当前优先级。每个线程都从其创建者继承优先级。thr_getprio()会将当前的优先级tid存储到newprio所指向的位置。对于POSIX线程,请参见第39页中的“pthread_getschedparam语法”。thr_getprio语法#includeintthr_getprio(thread_ttid,int*newprio)thr_getprio返回值thr_getprio()在成功运行后返回0。如果检测到以下情况,thr_getprio()将失败并返回对应的值。ESRCH描述:tid指定的值不会引用现有的线程。相似的同步函数-互斥锁■初始化互斥锁■销毁互斥锁■获取互斥锁■释放互斥锁■尝试获取互斥锁初始化互斥锁使用mutex_init(3C)可以初始化mp所指向的互斥锁。对于POSIX线程,请参见第92页中的“初始化互斥锁”。mutex_init(3C)语法#include#includeintmutex_init(mutex_t*mp,inttype,void*arg));210多线程编程指南•2006年10月 相似的同步函数-互斥锁type可以是以下值之一:■USYNC_PROCESS。互斥锁可用来同步此进程和其他进程中的线程。arg会被忽略。■USYNC_PROCESS_ROBUST。可使用互斥来在此进程和其他进程中可靠地同步线程。arg会被忽略。■USYNC_THREAD。互斥锁可用来仅同步此进程中的线程。arg会被忽略。如果进程在持有USYNC_PROCESS锁时失败,该锁以后的请求者都将挂起。对于与客户机进程共享锁的系统,此行为会产生问题,因为客户机进程会异常中止。为了避免出现停用进程所持有的锁定挂起的问题,请使用USYNC_PROCESS_ROBUST来锁定互斥锁。USYNC_PROCESS_ROBUST增加了两个功能:■如果进程停用,则系统将解除锁定该进程所持有的全部锁。■请求失败进程所拥有的任何锁的下一个请求者将获取该锁。但是,拥有该锁时会返回一个错误,指明以前的属主在拥有该锁时失败。互斥锁还可以通过在清零的内存中进行分配来初始化,在这种情况下假定type为USYNC_THREAD。多个线程决不能同时初始化同一个互斥锁。如果其他线程正在使用互斥锁,则不得将该互斥锁重新初始化。进程内的互斥锁#includemutex_tmp;intret;/*tobeusedwithinthisprocessonly*/ret=mutex_init(&mp,USYNC_THREAD,0);进程间的互斥锁#includemutex_tmp;intret;第8章•Solaris线程编程211 相似的同步函数-互斥锁/*tobeusedamongallprocesses*/ret=mutex_init(&mp,USYNC_PROCESS,0);强健的进程间互斥#includemutex_tmp;intret;/*tobeusedamongallprocesses*/ret=mutex_init(&mp,USYNC_PROCESS_ROBUST,0);mutex_init返回值mutex_init()在成功运行后返回0。如果检测到以下任一情况,mutex_init()将失败并返回对应的值。EFAULT描述:mp指向的地址非法。EINVAL描述:mp指定的值无效。ENOMEM描述:系统内存不足,无法初始化互斥锁。EAGAIN描述:系统资源不足,无法初始化互斥锁。EBUSY描述:系统检测到重新初始化活动互斥锁的尝试。212多线程编程指南•2006年10月 相似的同步函数-互斥锁销毁互斥锁使用mutex_destroy(3C)可以销毁与mp所指向的互斥锁相关联的任何状态。用来存储该互斥锁的空间不会释放。对于POSIX线程,请参见第98页中的“pthread_mutex_destroy语法”。mutex_destroy语法#includeintmutex_destroy(mutex_t*mp);mutex_destroy返回值mutex_destroy()在成功运行后返回0。如果检测到以下情况,mutex_destroy()将失败并返回对应的值。EFAULT描述:mp指向的地址非法。获取互斥锁使用mutex_lock(3C)可以锁定mp所指向的互斥锁。如果该互斥锁已经锁定,调用线程将会阻塞,直到该互斥锁成为可用为止。调用线程会在具有优先级的队列中等待。对于POSIX线程,请参见第94页中的“pthread_mutex_lock语法”。mutex_lock语法#includeintmutex_lock(mutex_t*mp);mutex_lock返回值mutex_lock()在成功运行后返回0。如果检测到以下任一情况,mutex_lock()将失败并返回对应的值。EFAULT描述:mp指向的地址非法。EDEADLK描述:互斥锁已经锁定并且由调用线程拥有。第8章•Solaris线程编程213 相似的同步函数-互斥锁释放互斥锁使用mutex_unlock(3C)可以解除锁定mp所指向的互斥锁。该互斥锁必须锁定。调用线程必须是最后一个锁定该互斥锁的线程,即该互斥锁的属主。对于POSIX线程,请参见第96页中的“pthread_mutex_unlock语法”。mutex_unlock语法#includeintmutex_unlock(mutex_t*mp);mutex_unlock返回值mutex_unlock()在成功运行后返回0。如果检测到以下任一情况,mutex_unlock()将失败并返回对应的值。EFAULT描述:mp指向的地址非法。EPERM描述:调用线程不拥有该互斥锁。尝试获取互斥锁使用mutex_trylock(3C)可以尝试锁定mp所指向的互斥锁。此函数是mutex_lock()的非阻塞版本。对于POSIX线程,请参见第97页中的“pthread_mutex_trylock语法”。mutex_trylock语法#includeintmutex_trylock(mutex_t*mp);mutex_trylock返回值mutex_trylock()在成功运行后返回0。如果检测到以下任一情况,mutex_trylock()将失败并返回对应的值。214多线程编程指南•2006年10月 相似的同步函数:条件变量EFAULT描述:mp指向的地址非法。EBUSY描述:系统检测到重新初始化活动互斥锁的尝试。相似的同步函数:条件变量■初始化条件变量■销毁条件变量■等待条件■等待绝对时间■等待时间间隔■解除阻塞一个线程■解除阻塞所有线程初始化条件变量使用cond_init(3C)可以初始化cv所指向的条件变量。cond_init语法#includeintcond_init(cond_t*cv,inttype,intarg);type可以是以下值之一:■USYNC_PROCESS。条件变量可用来同步此进程和其他进程中的线程。arg会被忽略。■USYNC_THREAD。条件变量可用来仅同步此进程中的线程。arg会被忽略。条件变量还可以通过在清零的内存中进行分配来初始化,在这种情况下假设type为USYNC_THREAD。多个线程决不能同时初始化同一个条件变量。对于其他线程可能正在使用的条件变量,不得重新初始化。对于POSIX线程,请参见第106页中的“pthread_condattr_init语法”。进程内条件变量#include第8章•Solaris线程编程215 相似的同步函数:条件变量cond_tcv;intret;/*tobeusedwithinthisprocessonly*/ret=cond_init(cv,USYNC_THREAD,0);进程间条件变量#includecond_tcv;intret;/*tobeusedamongallprocesses*/ret=cond_init(&cv,USYNC_PROCESS,0);cond_init返回值cond_init()在成功运行后返回0。如果检测到以下任一情况,cond_init()将失败并返回对应的值。EFAULT描述:cv指向的地址非法。EINVAL描述:type不是可以识别的类型。销毁条件变量使用cond_destroy(3C)可以销毁与cv所指向的条件变量相关联的状态。用来存储该条件变量的空间不会释放。对于POSIX线程,请参见第107页中的“pthread_condattr_destroy语法”。216多线程编程指南•2006年10月 相似的同步函数:条件变量cond_destroy语法#includeintcond_destroy(cond_t*cv);cond_destroy返回值cond_destroy()在成功运行后返回0。如果检测到以下任一情况,cond_destroy()将失败并返回对应的值。EFAULT描述:cv指向的地址非法。EBUSY描述:系统检测到销毁活动条件变量的尝试。等待条件使用cond_wait(3C)可以原子方式释放mp所指向的互斥锁,并导致调用线程基于cv所指向的条件变量阻塞。阻塞的线程可以由cond_signal()或cond_broadcast()唤醒,也可以在信号传送或fork()将其中断时唤醒。cond_wait()每次返回时,互斥锁均处于锁定状态并由调用线程拥有,即使返回错误时也是如此。cond_wait语法#includeintcond_wait(cond_t*cv,mutex_t*mp);cond_wait返回值cond_wait()在成功运行后返回0。如果检测到以下任一情况,cond_wait()将失败并返回对应的值。EFAULT描述:cv指向的地址非法。EBUSY描述:等待过程已被信号或fork()中断。第8章•Solaris线程编程217 相似的同步函数:条件变量等待绝对时间cond_timedwait(3C)与cond_wait()非常相似,区别在于cond_timedwait()经过abstime指定的时间之后不会阻塞。对于POSIX线程,请参见第114页中的“pthread_cond_timedwait语法”。cond_timedwait语法#includeintcond_timedwait(cond_t*cv,mutex_t*mp,timestruct_tabstime);cond_timedwait()每次返回时,互斥锁均会锁定并由调用线程拥有,即使返回错误时也是如此。cond_timedwait()函数会一直阻塞,直到该条件获得信号,或者经过最后一个参数所指定的时间为止。超时以具体的时间指定,这样即可在不重新计算超时值的情况下高效地重新测试条件。cond_timedwait返回值cond_timedwait()在成功运行后返回0。如果检测到以下任一情况,cond_timedwait()将失败并返回对应的值。EFAULT描述:cv指向的地址非法。ETIME描述:由abstime指定的时间已过期。EINVAL描述:abstime无效。等待时间间隔cond_reltimedwait(3C)与cond_timedwait()非常相似,区别在于第三个参数的值不同。cond_reltimedwait()的第三个参数采用相对时间间隔值,而不是绝对时间值。对于POSIX线程,请参见pthread_cond_reltimedwait_np(3C)手册页。cond_reltimedwait()每次返回时,互斥锁均会锁定并由调用线程拥有,即使返回错误时也是如此。cond_reltimedwait()函数一直阻塞,直到该条件获得信号,或者经过最后一个参数所指定的时间间隔为止。218多线程编程指南•2006年10月 相似的同步函数:条件变量cond_reltimedwait语法#includeintcond_reltimedwait(cond_t*cv,mutex_t*mp,timestruct_treltime);cond_reltimedwait返回值cond_reltimedwait()在成功运行后返回0。如果检测到以下任一情况,cond_reltimedwait()将失败并返回对应的值。EFAULT描述:cv指向的地址非法。ETIME描述:由reltime指定的时间已过期。解除阻塞一个线程对于基于cv所指向的条件变量阻塞的线程,使用cond_signal(3C)可以解除阻塞该线程。如果没有线程基于该条件变量阻塞,则调用cond_signal()不起任何作用。cond_signal语法#includeintcond_signal(cond_t*cv);cond_signal返回值cond_signal()在成功运行后返回0。如果检测到以下情况,cond_signal()将失败并返回对应的值。EFAULT描述:cv指向的地址非法。第8章•Solaris线程编程219 相似的同步函数:信号解除阻塞所有线程对于基于cv所指向的条件变量阻塞的全部线程,使用cond_broadcast(3C)可以解除阻塞这些线程。如果没有线程基于该条件变量阻塞,则调用cond_broadcast()不起任何作用。cond_broadcast语法#includeintcond_broadcast(cond_t*cv);cond_broadcast返回值cond_broadcast()在成功运行后返回0。如果检测到以下情况,cond_broadcast()将失败并返回对应的值。EFAULT描述:cv指向的地址非法。相似的同步函数:信号信号操作在Solaris操作环境和POSIX环境中均相同。Solaris操作环境中的函数名sema_在pthread中会更改为sem_。本节讨论了以下主题:■初始化信号■增加信号■基于信号计数阻塞■减小信号计数■销毁信号状态初始化信号使用sema_init(3C)可以通过count值来初始化sp所指向的信号变量。sema_init语法#includeintsema_init(sema_t*sp,unsignedintcount,inttype,void*arg);220多线程编程指南•2006年10月 相似的同步函数:信号type可以是以下值之一:■USYNC_PROCESS。信号可用来同步此进程和其他进程中的线程。信号只能由一个进程来初始化。arg会被忽略。■USYNC_THREAD。信号可用来仅同步此进程中的线程。arg会被忽略。多个线程决不能同时初始化同一个信号。不得对其他线程正在使用的信号重新初始化。进程内信号#includesema_tsp;intret;intcount;count=4;/*tobeusedwithinthisprocessonly*/ret=sema_init(&sp,count,USYNC_THREAD,0);进程间信号#includesema_tsp;intret;intcount;count=4;/*tobeusedamongalltheprocesses*/ret=sema_init(&sp,count,USYNC_PROCESS,0);第8章•Solaris线程编程221 相似的同步函数:信号sema_init返回值sema_init()在成功运行后返回0。如果检测到以下任一情况,sema_init()将失败并返回对应的值。EINVAL描述:sp引用的信号无效。EFAULT描述:sp或arg指向的地址非法。增加信号使用sema_post(3C)可以原子方式增加sp所指向的信号。如果多个线程基于该信号阻塞,则系统会解除阻塞其中一个线程。sema_post语法#includeintsema_post(sema_t*sp);sema_post返回值sema_post()在成功运行后返回0。如果检测到以下任一情况,sema_post()将失败并返回对应的值。EINVAL描述:sp引用的信号无效。EFAULT描述:sp指向的地址非法。EOVERFLOW描述:sp指向的信号值超过了SEM_VALUE_MAX。基于信号计数阻塞使用sema_wait(3C)可以一直阻塞调用线程,直到sp所指向的信号的计数变得大于零为止。计数变得大于零时,系统会以原子方式减小计数。222多线程编程指南•2006年10月 相似的同步函数:信号sema_wait语法#includeintsema_wait(sema_t*sp);sema_wait返回值sema_wait()在成功运行后返回0。如果检测到以下任一情况,sema_wait()将失败并返回对应的值。EINVAL描述:sp引用的信号无效。EINTR描述:等待过程已被信号或fork()中断。减小信号计数使用sema_trywait(3C)可以在计数大于零时,以原子方式减小sp所指向的信号的计数。此函数是sema_wait()的非阻塞版本。sema_trywait语法#includeintsema_trywait(sema_t*sp);sema_trywait返回值sema_trywait()在成功运行后返回0。如果检测到以下任一情况,sema_trywait()将失败并返回对应的值。EINVAL描述:sp指向的信号无效。EBUSY描述:sp所指向的信号的计数为零。第8章•Solaris线程编程223 跨进程边界同步销毁信号状态使用sema_destroy(3C)可以销毁与sp所指向的信号相关联的任何状态。不会释放用来存储该信号的空间。sema_destroy(3C)语法#includeintsema_destroy(sema_t*sp);sema_destroy(3C)返回值sema_destroy()在成功运行后返回0。如果检测到以下情况,sema_destroy()将失败并返回对应的值。EINVAL描述:sp指向的信号无效。跨进程边界同步每个同步元语都可以设置为跨进程边界使用。可通过以下方法来设置跨边界同步:确保同步变量位于共享内存段中,并在type设置为USYNC_PROCESS的情况下调用相应的init例程。如果type设置为USYNC_PROCESS,则针对同步变量执行的操作与type为USYNC_THREAD时针对变量执行的操作相同。mutex_init(&m,USYNC_PROCESS,0);rwlock_init(&rw,USYNC_PROCESS,0);cond_init(&cv,USYNC_PROCESS,0);sema_init(&s,count,USYNC_PROCESS,0);生成方和使用者问题示例示例8–2说明了生成方和使用者位于不同进程时的生成方和使用者问题。主例程将与其子进程共享的全零内存段映射到其地址空间。请注意,必须调用mutex_init()和cond_init(),因为同步变量的type为USYNC_PROCESS。创建子进程是为了运行使用者,父进程则运行生成方。224多线程编程指南•2006年10月 跨进程边界同步此示例还说明了生成方和使用者的驱动程序。producer_driver从stdin读取字符并调用producer。consumer_driver通过调用consumer来获取字符并将这些字符写入stdout中。示例8–2的数据结构与用于解析条件变量的数据结构相同。请参见第102页中的“嵌套锁定和单链接列表的结合使用示例”。示例8–2使用USYNC_PROCESS时的生成方和使用者问题main(){intzfd;buffer_t*buffer;zfd=open(“/dev/zero”,O_RDWR);buffer=(buffer_t*)mmap(NULL,sizeof(buffer_t),PROT_READ|PROT_WRITE,MAP_SHARED,zfd,0);buffer->occupied=buffer->nextin=buffer->nextout=0;mutex_init(&buffer->lock,USYNC_PROCESS,0);cond_init(&buffer->less,USYNC_PROCESS,0);cond_init(&buffer->more,USYNC_PROCESS,0);if(fork()==0)consumer_driver(buffer);elseproducer_driver(buffer);}voidproducer_driver(buffer_t*b){intitem;第8章•Solaris线程编程225 跨进程边界同步示例8–2使用USYNC_PROCESS时的生成方和使用者问题(续)while(1){item=getchar();if(item==EOF){producer(b,‘’);break;}elseproducer(b,(char)item);}}voidconsumer_driver(buffer_t*b){charitem;while(1){if((item=consumer(b))==’’)break;putchar(item);}}创建子进程是为了运行使用者,父进程则运行生成方。226多线程编程指南•2006年10月 fork()和Solaris线程的特殊问题fork()和Solaris线程的特殊问题在Solaris10发行版之前,Solaris线程和POSIX线程以不同的方式定义fork()的行为。有关fork()问题的详细讨论,请参见第152页中的“进程创建:exec和exit问题”。Solarislibthread同时支持fork()和fork1()。fork()调用具有"Fork-All"语义。fork()可用来复制进程中的所有内容(包括线程和LWP),从而创建父进程的准确克隆。fork1()调用所创建的克隆中仅有一个线程,它可复制进程状态和地址空间,但是仅克隆调用线程。POSIXlibpthread仅支持fork(),该函数与Solaris线程中的fork1()具有相同语义。fork()具有"Fork-All"语义还是"Fork-One"语义取决于所使用的库。使用-lthread进行链接可以赋予fork()"Fork-All"语义;使用-lpthread进行链接可以赋予fork()"Fork-One"语义。从Solaris10发行版开始,fork()在Solaris线程和POSIX线程中具有相同的语义。具体来说,fork1()语义仅复制调用方。对于需要使用“复制全部”语义的应用程序,提供了一个新函数forkall()。有关更多详细信息,请参见第179页中的“使用libthread或libpthread链接”。第8章•Solaris线程编程227 228 第99章编程原则本章提供有关使用线程进行编程的一些建议。大多数建议同时适用于Solaris和POSIX线程,但效用会有所不同,需要注意其行为。本章着重讲解从单线程思维到多线程思维的转变。本章讨论以下主题:■第229页中的“重新考虑全局变量”■第230页中的“提供静态局部变量”■第231页中的“同步线程”■第234页中的“避免死锁”■第236页中的“线程代码的一些基本原则”■第236页中的“创建和使用线程”■第237页中的“使用多处理器”■第241页中的“线程程序示例”重新考虑全局变量以前,大多数代码都是为单线程程序设计的。此代码设计特别适合于大多数从C程序调用的库例程。对于单线程代码,进行了以下隐含假设:■写入全局变量,随后又从该变量中读取时,读取的内容就是写入的内容。■写入非全局静态存储,随后又从变量中读取时,所读取的内容恰好就是写入的内容。■不需要进行同步,因为不会调用对变量的并发访问。以下示例论述了由于这些假设而在多线程程序中引发的一些问题,以及如何处理这些问题。传统的单线程C和UNIX通常会处理在系统调用中检测到的错误。系统调用可将任何内容作为函数值返回。例如,write()返回已传输的字节数。但是,会保留值-1以表明出现了错误。因此,当系统调用返回-1时,即表明调用失败。229 提供静态局部变量示例9–1全局变量和errnoexterninterrno;...if(write(file_desc,buffer,size)==-1){/*thesystemcallfailed*/fprintf(stderr,“somethingwentwrong,““errorcode=%d ”,errno);exit(1);}...错误代码将被置于全局变量errno中,而不是返回可能与正常返回值混淆的实际错误代码。系统调用失败时,可以查看errno以了解错误所在。现在,请考虑在多线程环境中,当两个线程几乎同时失败而出现的错误不同时会发生的情况。两个线程都期望在errno中找到其错误代码,但errno的一个副本不能同时包含两个值。此全局变量方法不适用于多线程程序。线程可通过概念上的新存储类来解决此问题:线程特定数据。此类存储似于全局存储。可从正在运行的线程的任何过程访问线程特定数据。但是,线程特定数据专门用于线程。当两个线程引用同名称的线程特定数据位置时,这些线程引用的是两个不同的存储区域。因此,使用线程时,对errno的每个引用都是特定于线程的,因为每个线程都具有专用的errno副本。在此实现方案中,通过使errno成为可扩展到函数调用的宏来实现特定于线程的errno调用。提供静态局部变量示例9–2说明了与errno问题类似的问题,但涉及的是静态存储,而不是全局存储。函数gethostbyname(3NSL)是将计算机名称作为其参数进行调用的。返回值是一个指向结构的指针,该结构包含通过网络通信联系计算机所需的信息。示例9–2gethostbyname()问题structhostent*gethostbyname(char*name){230多线程编程指南•2006年10月 同步线程示例9–2gethostbyname()问题(续)staticstructhostentresult;/*Lookupnameinhostsdatabase*//*Putanswerinresult*/return(&result);}通常情况下,使用返回到局部变量的指针不是一个好办法。在本示例中使用指针有效,是因为变量是静态的。但是,当两个线程同时使用不同的计算机名称调用此变量时,使用静态存储会发生冲突。与errno问题一样,可以使用线程特定数据来替换静态存储。但是,此替换涉及到动态分配存储,并且会增加调用开支。处理该问题的更好方法是使gethostbyname()的调用方为调用结果提供存储。调用方可通过例程的其他输出参数来提供存储。其他输出参数需要gethostbyname()函数的新接口。在线程中常使用此技术来解决许多问题。在大多数情况下,新接口的名称就是原有名称附加"_r",如gethostbyname_r(3NSL)。同步线程共享数据和进程资源时,应用程序中的线程必须彼此协作并进行同步。多个线程调用处理同一对象的函数时,会引发问题。在单线程环境中,同步对这类对象的访问不是问题。但是,如示例9–3所示,同步对于多线程代码是个问题。请注意,对于多线程程序,可以安全调用printf(3S)函数。本示例说明当printf()不安全时可能会发生的情况。示例9–3printf()问题/*thread1:*/printf("gotostatementreached");第9章•编程原则231 同步线程示例9–3printf()问题(续)/*thread2:*/printf("helloworld");printedondisplay:gotohello单线程策略一个策略是,只要应用程序中的任何线程处于运行状态并在必须阻塞之前被释放,即可获取单个应用程序范围的互斥锁。由于无论何时都只能有一个线程可以访问共享数据,因此每个线程都有一致的内存视图。由于此策略仅对单线程非常有效,因此此策略的使用范围非常小。可重复执行函数更好的方法是利用模块化和数据封装的原理。可重复执行函数可以在同时被多个线程调用的情况下正确执行。要编写可重复执行函数,需要大致了解正确操作对此特定函数的意义。必须使被多个线程调用的函数可重复执行。要使函数可重复执行,可能需要对函数接口或实现进行更改。访问全局状态(如内存或文件)的函数具有可重复执行问题。这些函数需要借助线程提供的相应同步机制来保护其全局状态的使用。使模块中的函数可重复执行的两个基本策略是代码锁定和数据锁定。代码锁定代码锁定是在函数调用级别执行的,而且可保证函数在锁定保护下完全执行。该假设针对通过函数对数据执行的所有访问。共享数据的函数应该在同一锁定下执行。某些并行编程语言提供一种构造,称为监视程序。监视程序可以对监视程序范围内定义的函数隐式执行代码锁定。还可以通过互斥锁来实现监视。232多线程编程指南•2006年10月 同步线程可保证受同一互斥锁保护或同一监视程序中的函数相对于监视程序中的其他函数以原子方式执行。数据锁定数据锁定可保证一直维护对数据集合的访问。对于数据锁定,代码锁定概念仍然存在,但代码锁定只是对共享(全局)数据的轮流引用。对于互斥锁,在每个数据集合的临界段中只能有一个线程。另外,在多个读取器、单个写入器协议中,允许每个数据集合或一个写入器具有多个读取器。当多个线程对不同数据集合执行操作时,这些线程可以在单个模块中执行。需要特别指出的是,对于多个读取器、单个写入器协议,这些线程不会在单个集合上发生冲突。因此,数据锁定通常比代码锁定具备的并发性更多。使用锁定时应使用哪种策略,在程序中实现互斥、条件变量还是信号?是要尝试通过仅在必要时锁定并在不必要时尽快解除锁定来实现最大并行性(这种方法称作“细粒度锁定(fine-grainedlocking)”)?还是要长期持有锁定,以使使用和释放锁的开销降到最低程度(这种方法称作“粗粒度锁定(coarse-grainedlocking)”)?锁定的粒度取决于锁定所保护的数据量。粒度非常粗的锁定可能是单一锁定,目的是保护所有数据。划分由适当数目的锁定保护数据的方式非常重要。锁定粒度过细可能会降低性能。当应用程序包含太多锁定时,与获取和释放锁关联的开销可能会变得非常大。常见的明智之举是先使用粗粒度方法,确定瓶颈,并在必要时添加细粒度锁定来缓解瓶颈。此方法听起来是很合理的建议,但是您应该自行判断如何在最大化并行性与最小化锁定开销之间找到平衡。不变量和锁定对于代码锁定和数据锁定,不变量对于控制锁定复杂性非常重要。不变量指始终为真的条件或关系。对于并发执行,其定义修改如下(在上述定义的基础上稍加修改即可得到此定义):不变量是在设置关联锁定时为真的条件或关系。设置锁定后,不变量可能为假。但是,在释放锁之前,持有锁的代码必须重新建立不变量。不变量还可以是设置锁定时为真的条件或关系。条件变量可以被认为含有一个不变量,而这个不变量就是这个条件。示例9–4使用assert(3X)测试不变量mutex_lock(&lock);while((condition)==FALSE)cond_wait(&cv,&lock);第9章•编程原则233 避免死锁示例9–4使用assert(3X)测试不变量(续)assert((condition)==TRUE);...mutex_unlock(&lock);assert()语句用于测试不变量。cond_wait()函数不保留不变量,这就是在线程返回时必须重新评估不变量的原因所在。另一个示例就是用于管理双重链接的元素列表的模块。对于该列表中的每一项,良好的不变量是列表中前一项的向前指针。向前指针还应与向前项的向后指针指向同一元素。假设此模块使用基于代码的锁定,进而受到单个全局互斥锁的保护。删除或添加项时,将获取互斥锁,正确处理指针,而且会释放互斥锁。显然,在处理指针的某一时间点,不变量为假,但在释放互斥锁之前,需要重新建立不变量。避免死锁死锁是指永久阻塞一组争用一组资源的线程。仅因为某个线程可以继续执行,并不表示不会在某个其他位置发生死锁。导致死锁的最常见错误是自死锁或递归死锁。在自死锁或递归死锁中,线程尝试获取已被其持有的锁。递归死锁是在编程时很容易犯的错误。例如,假设代码监视程序让每个模块函数在调用期间都获取互斥锁。随后,由互斥锁保护的模块内函数间的任何调用都会立即死锁。函数调用模块外的代码时,如果迂回调入任何受同一互斥锁保护的方法,则该函数也会发生死锁。这种死锁的解决方案就是避免调用模块外可能通过某一路径依赖此模块的函数。需要特别指出的是,应避免在未重新建立不变量的情况下调用回调入模块的函数,而且在执行调用之前不要删除所有的模块锁。当然,在调用完成和重新获取锁定之后,必须验证状态,以确保预期的操作仍然有效。另一种死锁的示例就是当两个线程(线程1和线程2)分别获取互斥锁A和B时的情况。假设,线程1尝试获取互斥锁B,线程2尝试获取互斥锁A。如果线程1在等待互斥锁B时受到阻塞,而无法继续执行。线程2在等待互斥锁A时受到阻塞也无法继续执行。任何情况都不会发生变化。因此,在这种情况下,将永久阻塞线程,即出现死锁现象。234多线程编程指南•2006年10月 避免死锁通过建立获取锁定的顺序(锁定分层结构),可以避免这种死锁。当所有线程始终按指定的顺序获取锁定时,即可避免这种死锁。遵循严格的锁定获取顺序并不总是非常理想。例如,线程2具有许多有关在持有互斥锁B时模块状态的假设。放弃互斥锁B以获取互斥锁A,然后按相应的顺序重新获取互斥锁B将导致线程放弃其假设。必须重新评估模块的状态。阻塞同步元语通常具有变体,这些变体将尝试获取锁定,并在无法获取锁定时失败。例如mutex_trylock()。元语变体的这种行为允许线程在不出现争用时破坏锁定分层结构。出现争用时,通常必须放弃持有的锁定,并按顺序重新获取锁定。与调用相关的死锁由于不能保证获取锁定的顺序,因此如果特定线程永远不能获取锁定就会出现问题。持有锁的线程释放锁,一小段时间后重新获取锁定时,通常会出现此问题。由于锁被释放,因此其他线程似乎理应可以获取锁。但是,持有锁的线程未被阻塞。因此,从线程释放锁到重新获取锁定的时间内,该线程将持续运行。这样,就不会运行其他线程。通常,通过在进行重新获取锁定的调用前调用thr_yield(3C),可以解决此类型的问题。thr_yield()允许其他线程运行并获取锁定。由于应用程序的时间片要求是可变的,因此系统不会强加任何要求。可通过调用thr_yield()来使线程根据需要进行分时操作。锁定原则请遵循以下的简单锁定原则。■请勿尝试在可能会对性能造成不良影响的长时间操作(如I/O)中持有锁。■请勿在调用模块外且可能重进入模块的函数时持有锁。■一般情况下,请先使用粗粒度锁定方法,确定瓶颈,并在必要时添加细粒度锁定来缓解瓶颈。大多数锁定都是短期持有,而且很少出现争用。因此,请仅修复测得争用的那些锁定。■使用多个锁定时,通过确保所有线程都按相同的顺序获取锁定来避免死锁。第9章•编程原则235 线程代码的一些基本原则线程代码的一些基本原则■了解要导入的内容并了解其是否安全。线程程序不能随意输入非线程代码。■线程代码只能从初始线程中安全引用不安全的代码。以此方式引用不安全的代码确保了仅该线程使用与初始线程关联的静态存储。■使库可以安全地用于多线程时,请勿通过线程执行全局进程操作。请不要将全局操作或具有全局负面影响的操作更改为以线程方式执行。例如,如果将文件I/O更改为每线程操作,则线程无法在访问文件时进行协作。对于线程特定的行为或线程识别的行为,请使用线程功能。例如,终止main()时,应该仅终止将退出main()的线程。thr_exit();/*NOTREACHED*/■除非明确说明Sun提供的库是安全的,否则假定这些库不安全。如果参考手册项没有明确声明接口是MT-Safe,则假定接口不安全。■请使用编译标志来管理二进制不兼容源代码更改。有关完整的说明,请参见第7章。■-D_REENTRANT启用多线程。■-D_POSIX_C_SOURCE提供POSIX线程行为。■-D_POSIX_PTHREADS_SEMANTICS启用Solaris线程和pthread接口。当这两个接口发生冲突时,将优先使用POSIX接口。创建和使用线程线程软件包会对线程数据结构和栈进行高速缓存,以使重复创建线程的代价较为合理。但是,与管理等待独立工作的线程池相比,在需要线程时创建和销毁线程的代价通常会更高。RPC服务器就是一个很好的示例,该服务器可以为每个请求创建一个线程,并在传送回复时销毁该线程。创建线程的开销比创建进程的开销要少。但是,与创建几个指令的成本相比,创建线程并不是最经济的。请在至少持续处理数千个计算机指令时创建线程。236多线程编程指南•2006年10月 使用多处理器使用多处理器借助多线程,可以充分利用多处理器,主要是通过并行性和可伸缩性。程序员应该了解多处理器与单处理器的内存模块之间的差异。内存一致性与询问内存的处理器直接相关。对于单处理器,内存显然是一致的,因为只有一个处理器查看内存。要提高多处理器性能,应放宽内存一致性。不应始终假设由一个处理器对内存所做的更改立即反映在该内存的其他处理器视图中。使用共享变量或全局变量时,可借助同步变量来避免这种复杂性。屏障同步有时是控制多处理器上并行性的一种有效方式。可以在附录B中找到屏障示例。另一个多处理器问题是在线程必须等到所有线程都到达执行的共同点时进行有效同步。注–始终使用线程同步元语访问共享内存位置时,此处讨论的问题并不重要。基础体系结构线程使用线程同步例程来同步对共享存储位置的访问。使用线程同步时,在共享内存多处理器上运行程序与在单处理器上运行程序的效果相同。但是,在许多情况下,程序员可能很想利用多处理器,并使用“技巧”来避免同步例程。正如示例9–5和示例9–6所示,这类技巧可能是危险的。了解常见多处理器体系结构支持的内存模块有助于了解这类危险。主要的多处理器组件为:■运行程序的处理器■存储缓冲区,将处理器连接至其高速缓存■高速缓存,存放最近访问或修改的存储位置的内容■内存,主存储区且供所有处理器共享在简单的传统模型中,多处理器的行为方式就像将处理器直接连接到内存一样:当一个处理器存储在一个位置,而另一个处理器从同一位置直接装入时,第二个处理器将装入第一个处理器存储的内容。可以使用高速缓存来加快平均内存访问速度。当该高速缓存与其他高速缓存保持一致时,即可实现所需的语义。该简单方法存在的问题是,必须经常延迟处理器以确定是否已实现所需的语义。许多现代的多处理器使用各种技术来防止这类延迟,遗憾的是,这会更改内存模型的语义。第9章•编程原则237 使用多处理器下面的两个示例说明了这两种方法及其效果。“共享内存”多处理器请考虑示例9–5中显示的对生成方和使用者问题的假设解决方案。尽管此程序是在当前基于SPARC的多处理器上工作,但该程序假设所有的多处理器都有强秩序存储器(stronglyorderedmemory)。因此,此程序不是可移植的。示例9–5生成方和使用者问题:共享内存多处理器charbuffer[BSIZE];unsignedintin=0;unsignedintout=0;voidcharproducer(charitem){consumer(void){charitem;do;/*nothing*/dowhile;/*nothing*/(in-out==BSIZE);while(in-out==0);buffer[in%BSIZE]=item;item=buffer[out%BSIZE];in++;out++;}}当此程序确实具有一个生成方和一个使用者,而且在共享内存多处理器上运行时,该程序看上去是正确的。in与out之间的差异就是缓冲区中的项数目。生成方通过重复计算此差异一直等待,直到缓冲区中有可用空间来存放新项为止。使用者一直等到缓冲区中存在项为止。238多线程编程指南•2006年10月 使用多处理器严格排序的内存可以对一个处理器上可供其他处理器直接使用的内存进行修改。对于强秩序存储器,该解决方案是正确的,即使考虑到in和out最终会溢出也是如此。只要BSIZE小于以单个词表示的最大整数,就会发生溢出。共享内存多处理器不一定具有强秩序存储器。由一个处理器对内存所做的更改不一定可直接用于其他处理器。请了解一个处理器对不同内存位置进行两次更改时发生的具体情况。其他处理器不一定会按照更改顺序检测更改,因为不会立即修改内存。首先,会将更改存储在对于高速缓存不可见的存储缓冲区中。处理器将检查这些存储缓冲区,以确保程序具有一致的视图。但是,由于存储缓冲区对于其他处理器是不可见的,因此在某个处理器写入高速缓存之前,该处理器写入的内容不可见。同步元语使用刷新存储缓冲区的特殊指令来执行高速缓存。请参见第4章。因此,对共享数据使用锁定可确保内存的一致性。如果内存排序非常宽松,则示例9–5存在问题。使用者可能发现,在其查看对相应缓冲槽位所做的更改之前生成方已经增大了in。此排序称为弱排序,因为由一个处理器执行的存储在由另一个处理器执行时顺序可能会被打乱。但是,内存在同一个处理器中始终是一致的。要解决此不一致性,代码应使用互斥来刷新高速缓存。由于趋势朝着放宽内存顺序方向发展,因此程序员在对所有的全局数据或共享数据使用锁定时变得越来越谨慎。如示例9–5和示例9–6所示,锁定是最基本的。Peterson算法示例9–6中的代码是Peterson算法的实现,该算法可以处理两个线程之间的互斥。此代码尝试保证临界段中只有一个线程。当线程调用mut_excl()时,线程会在某一时间“快速”进入临界段。此处假设在进入临界段后线程很快就退出了。示例9–6两个线程是否互斥?voidmut_excl(intme/*0or1*/){staticintloser;staticintinterested[2]={0,0};intother;/*localvariable*/第9章•编程原则239 使用多处理器示例9–6两个线程是否互斥?(续)other=1-me;interested[me]=1;loser=me;while(loser==me&&interested[other]);/*criticalsection*/interested[me]=0;}如果多处理器具有强秩序存储器,则此算法可工作一段时间。某些多处理器(包括某些基于SPARC的多处理器)具有存储缓冲区。线程发出存储指令时,数据即被放入存储缓冲区中。缓冲区内容最终会被发送到高速缓存,但不一定会立即发送。每个处理器上的高速缓存都可以维护一致的内存视图,但修改的数据不会立即到达高速缓存。在存储到多个内存位置时,更改将按正确顺序到达高速缓存和内存,但可能会在一定延迟后到达。具备此属性的基于SPARC的多处理器被称为具有全存储序顺序(totalstoreorder,TSO)。假设您面临的情况是,一个处理器存储到位置A并从位置B装入。另一个处理器存储到位置B并从位置A装入。第一个处理器将从位置B提取新修改的值,或第二个处理器将从位置A提取新修改的值,或者同时发生这两种情况。但是,两个处理器装入原有值的情况不会发生。此外,由于装入和存储缓冲区导致的延迟,可能会发生“不可能的情况”。使用Peterson算法时可能发生的情况是在不同处理器上运行的两个线程可以同时进入临界段。每个线程都可以存储到其各自的特定数组槽中,然后从其他槽装入。两个线程可以读取原有的值(0),每个线程都假设对方不存在,而且都可以进入临界段。测试程序时可能不会检测出这种问题,这种问题只会在稍后发生。要避免此问题,请使用线程同步元语,其实现会发出特殊指令,从而强制将存储缓冲区写入高速缓存。240多线程编程指南•2006年10月 线程程序示例在共享内存并行计算机上并行化循环在许多应用程序(特别是数值应用程序)中,一部分算法可以并行化,而其他部分却具有固有的顺序性,如下表所示。表9–1顺序执行并行执行Thread1Thread2throughThreadnwhile(many_iterations){while(many_iterations){sequential_computation---Barrier------Barrier---parallel_computationparallel_computation}}例如,您可能会使用严格的线性计算产生一组矩阵,并对使用并行算法的矩阵执行操作。随后,可以使用这些操作的结果来产生另一组矩阵,并行在这些矩阵上执行操作等。这类计算的并行算法特征是计算期间很少需要执行同步。但是,为确保在并行计算开始之前完成顺序计算,需要对所有线程进行同步。屏障将强制所有执行并行计算的线程一直等待,直到所有涉及到的线程都达到屏障为止。所有线程到达屏障后,即释放线程并同时开始计算。线程程序示例本指南介绍了各种重要线程编程问题。附录A提供了使用许多已论述功能和样式的pthread程序示例。附录B提供了使用Solaris线程的程序示例。需要进一步阅读的内容有关多线程的更详细信息,请参见SteveKleiman、DevangShah和BartSmaalders合编的《ProgrammingwithThreads》(Prentice-Hall出版,1995)。第9章•编程原则241 242 附录AA样例应用程序:多线程grep本附录提供样例程序tgrep,其中显示了find(1)与grep(1)组合后的多线程版本。tgrep的说明tgrep支持除常规grep的-w单词搜索选项和可独占使用选项以外的所有选项。缺省情况下,tgrep搜索与以下命令类似:find.-execgrep[options]pattern{};对于大型目录分层结构,tgrep比find命令获取结果的速度更快,具体速度取决于可用的处理器数目。在单处理器计算机上,tgrep的速度大约是find的两倍;在包含四个处理器的计算机上,tgrep的速度大约是find的四倍。-e选项可用于更改tgrep解释模式字符串的方式。通常,在不使用-e选项的情况下,tgrep将使用文字字符串匹配。如果使用-e选项,tgrep将使用正则表达式处理程序的MT-Safe(多线程安全)公共域版本。正则表达式方法的速度比较慢。tgrep-B选项可以使用TGLIMIT环境变量的值来限制搜索期间使用的线程的数目。如果未设置TGLIMIT,则此选项没有效果。由于tgrep可以使用许多系统资源,因而使用-B选项是在分时系统上适当运行tgrep的一种方式。注–Catalyst开发者的CD中包括tgrep的源代码。要了解如何获取副本,请与销售代表联系。此处仅出现了多线程main.c模块。可以在Catalyst开发者的CD中找到其他模块(包括用于处理正则表达式的其他模块),以及文档和make程序的描述文件。243 tgrep的说明示例A–1tgrep程序的源代码/*Copyright(c)1993,1994RonWinacott*//*Thisprogrammaybeused,copied,modified,andredistributedfreely*//*forANYpurpose,solongasthisnoticeremainsintact.*/#define_REENTRANT#include#include#include#include#include#include#include#include#include#include#include#include"version.h"#include#include#include244多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)#include#ifdefMARK#include/*toturnonMARK(),use-DMARKtocompile(seemanprof5)*/#endif#include"pmatch.h"#definePATH_MAX1024/*max#ofcharactersinapathname*/#defineHOLD_FDS6/*stdin,out,errandabuffer*/#defineUNLIMITED99999/*Thedefaulttglimit*/#defineMAXREGEXP10/*maxnumberof-eoptions*/#defineFB_BLOCK0x00001#defineFC_COUNT0x00002#defineFH_HOLDNAME0x00004#defineFI_IGNCASE0x00008#defineFL_NAMEONLY0x00010#defineFN_NUMBER0x00020#defineFS_NOERROR0x00040#defineFV_REVERSE0x00080#defineFW_WORD0x00100#defineFR_RECUR0x00200附录A•样例应用程序:多线程grep245 tgrep的说明示例A–1tgrep程序的源代码(续)#defineFU_UNSORT0x00400#defineFX_STDIN0x00800#defineTG_BATCH0x01000#defineTG_FILEPAT0x02000#defineFE_REGEXP0x04000#defineFS_STATS0x08000#defineFC_LINE0x10000#defineTG_PROGRESS0x20000#defineFILET1#defineDIRT2typedefstructwork_st{char*path;inttp;structwork_st*next;}work_t;typedefstructout_st{char*line;intline_count;longbyte_count;structout_st*next;246多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)}out_t;#defineALPHASIZ128typedefstructbm_pattern{/*Boyer-Moorepattern*/shortp_m;/*lengthofpatternstring*/shortp_r[ALPHASIZ];/*"r"vector*/short*p_R;/*"R"vector*/char*p_pat;/*patternstring*/}BM_PATTERN;/*bmpmatch.c*/externBM_PATTERN*bm_makepat(char*p);externchar*bm_pmatch(BM_PATTERN*pat,registerchar*s);externvoidbm_freepat(BM_PATTERN*pattern);BM_PATTERN*bm_pat;/*theglobaltargetreadonlyaftermain*//*pmatch.c*/externchar*pmatch(registerPATTERN*pattern,registerchar*string,int*len);externPATTERN*makepat(char*string,char*metas);externvoidfreepat(registerPATTERN*pat);externvoidprintpat(PATTERN*pat);PATTERN*pm_pat[MAXREGEXP];/*globaltargetsreadonlyforpmatch*/附录A•样例应用程序:多线程grep247 tgrep的说明示例A–1tgrep程序的源代码(续)#include"proto.h"/*functionprototypesofmain.c*//*localfunctionstoPOSIXthreadsonly*/voidpthread_setconcurrency_np(intcon);intpthread_getconcurrency_np(void);voidpthread_yield_np(void);pthread_attr_tdetached_attr;pthread_mutex_toutput_print_lk;pthread_mutex_tglobal_count_lk;intglobal_count=0;work_t*work_q=NULL;pthread_cond_twork_q_cv;pthread_mutex_twork_q_lk;pthread_mutex_tdebug_lock;#include"debug.h"/*mustbeincludedAFTERthemutex_tdebug_lockline*/work_t*search_q=NULL;pthread_mutex_tsearch_q_lk;248多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)pthread_cond_tsearch_q_cv;intsearch_pool_cnt=0;/*thecountinthepoolnow*/intsearch_thr_limit=0;/*themaxinthepool*/work_t*cascade_q=NULL;pthread_mutex_tcascade_q_lk;pthread_cond_tcascade_q_cv;intcascade_pool_cnt=0;intcascade_thr_limit=0;intrunning=0;pthread_mutex_trunning_lk;pthread_mutex_tstat_lk;time_tst_start=0;intst_dir_search=0;intst_file_search=0;intst_line_search=0;intst_cascade=0;intst_cascade_pool=0;intst_cascade_destroy=0;intst_search=0;intst_pool=0;附录A•样例应用程序:多线程grep249 tgrep的说明示例A–1tgrep程序的源代码(续)intst_maxrun=0;intst_worknull=0;intst_workfds=0;intst_worklimit=0;intst_destroy=0;intall_done=0;intwork_cnt=0;intcurrent_open_files=0;inttglimit=UNLIMITED;/*if-Blimitthenumberofthreads*/intprogress_offset=1;intprogress=0;/*protectedbytheprint_lock!*/unsignedintflags=0;intregexp_cnt=0;char*string[MAXREGEXP];intdebug=0;intuse_pmatch=0;charfile_pat[255];/*filepattenmatch*/PATTERN*pm_file_pat;/*compiledfiletargetstring(pmatch())*//**Main:Thisiswherethefunstarts250多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)*/intmain(intargc,char**argv){intc,out_thr_flags;longmax_open_files=0l,ncpus=0l;externintoptind;externchar*optarg;intprio=0;structstatsbuf;pthread_ttid,dtid;void*status;char*e=NULL,*d=NULL;/*fordebugflags*/intdebug_file=0;structsigactionsigact;sigset_tset,oset;interr=0,i=0,pm_file_len=0;work_t*work;intrestart_cnt=10;/*NOOTHERTHREADSARERUNNING*/flags=FR_RECUR;/*thedefault*/附录A•样例应用程序:多线程grep251 tgrep的说明示例A–1tgrep程序的源代码(续)while((c=getopt(argc,argv,"d:e:bchilnsvwruf:p:BCSZzHP:"))!=EOF){switch(c){#ifdefDEBUGcase’d’:debug=atoi(optarg);if(debug==0)debug_usage();d=optarg;fprintf(stderr,"tgrep:Debugonatlevel(s)");while(*d){for(i=0;i<9;i++)if(debug_set[i].level==*d){debug_levels|=debug_set[i].flag;fprintf(stderr,"%c",debug_set[i].level);break;}d++;}fprintf(stderr," ");break;case’f’:debug_file=atoi(optarg);break;#endif/*DEBUG*/252多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)case’B’:flags|=TG_BATCH;#ifndef__lock_lint/*locklintcomplainshere,buttherearenootherthreads*/if((e=getenv("TGLIMIT"))){tglimit=atoi(e);}else{if(!(flags&FS_NOERROR))/*orderdependent!*/fprintf(stderr,"envTGLIMITnotset,overriding-B ");flags&=~TG_BATCH;}#endifbreak;case’p’:flags|=TG_FILEPAT;strcpy(file_pat,optarg);pm_file_pat=makepat(file_pat,NULL);break;case’P’:flags|=TG_PROGRESS;progress_offset=atoi(optarg);附录A•样例应用程序:多线程grep253 tgrep的说明示例A–1tgrep程序的源代码(续)break;case’S’:flags|=FS_STATS;break;case’b’:flags|=FB_BLOCK;break;case’c’:flags|=FC_COUNT;break;case’h’:flags|=FH_HOLDNAME;break;case’i’:flags|=FI_IGNCASE;break;case’l’:flags|=FL_NAMEONLY;break;case’n’:flags|=FN_NUMBER;break;case’s’:flags|=FS_NOERROR;break;case’v’:flags|=FV_REVERSE;break;case’w’:flags|=FW_WORD;break;case’r’:flags&=~FR_RECUR;break;case’C’:flags|=FC_LINE;break;case’e’:if(regexp_cnt==MAXREGEXP){fprintf(stderr,"Maxnumberofregexp’s(%d)exceeded! ",MAXREGEXP);exit(1);}flags|=FE_REGEXP;if((string[regexp_cnt]=(char*)malloc(strlen(optarg)+1))==NULL){fprintf(stderr,"tgrep:Nospaceforsearchstring(s) ");exit(1);254多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)}memset(string[regexp_cnt],0,strlen(optarg)+1);strcpy(string[regexp_cnt],optarg);regexp_cnt++;break;case’z’:case’Z’:regexp_usage();break;case’H’:case’?’:default:usage();}}if(flags&FS_STATS)st_start=time(NULL);if(!(flags&FE_REGEXP)){if(argc-optind<1){fprintf(stderr,"tgrep:Mustsupplyasearchstring(s)""andfilelistordirectory ");usage();}if((string[0]=(char*)malloc(strlen(argv[optind])+1))==NULL){附录A•样例应用程序:多线程grep255 tgrep的说明示例A–1tgrep程序的源代码(续)fprintf(stderr,"tgrep:Nospaceforsearchstring(s) ");exit(1);}memset(string[0],0,strlen(argv[optind])+1);strcpy(string[0],argv[optind]);regexp_cnt=1;optind++;}if(flags&FI_IGNCASE)for(i=0;i10) ");exit(1);}search_thr_limit=max_open_files-HOLD_FDS-debug_file;cascade_thr_limit=search_thr_limit/2;/*thenumberoffilesthatcanbeopen*/current_open_files=search_thr_limit;pthread_attr_init(&detached_attr);pthread_attr_setdetachstate(&detached_attr,PTHREAD_CREATE_DETACHED);pthread_mutex_init(&global_count_lk,NULL);pthread_mutex_init(&output_print_lk,NULL);pthread_mutex_init(&work_q_lk,NULL);pthread_mutex_init(&running_lk,NULL);pthread_cond_init(&work_q_cv,NULL);附录A•样例应用程序:多线程grep257 tgrep的说明示例A–1tgrep程序的源代码(续)pthread_mutex_init(&search_q_lk,NULL);pthread_cond_init(&search_q_cv,NULL);pthread_mutex_init(&cascade_q_lk,NULL);pthread_cond_init(&cascade_q_cv,NULL);if((argc==optind)&&((flags&TG_FILEPAT)||(flags&FR_RECUR))){add_work(".",DIRT);flags=(flags&~FX_STDIN);}for(;optindnext;/*maybeNULL*/work->next=NULL;current_open_files--;pthread_mutex_unlock(&work_q_lk);tid=0;switch(work->tp){caseDIRT:pthread_mutex_lock(&cascade_q_lk);if(cascade_pool_cnt){if(flags&FS_STATS){pthread_mutex_lock(&stat_lk);st_cascade_pool++;pthread_mutex_unlock(&stat_lk);}work->next=cascade_q;cascade_q=work;pthread_cond_signal(&cascade_q_cv);262多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)pthread_mutex_unlock(&cascade_q_lk);DP(DLEVEL2,("Sentworktocascadepoolthread "));}else{pthread_mutex_unlock(&cascade_q_lk);err=pthread_create(&tid,&detached_attr,cascade,(void*)work);DP(DLEVEL2,("Sentworktonewcascadethread "));if(flags&FS_STATS){pthread_mutex_lock(&stat_lk);st_cascade++;pthread_mutex_unlock(&stat_lk);}}break;caseFILET:pthread_mutex_lock(&search_q_lk);if(search_pool_cnt){if(flags&FS_STATS){pthread_mutex_lock(&stat_lk);st_pool++;pthread_mutex_unlock(&stat_lk);}work->next=search_q;/*couldbenull*/附录A•样例应用程序:多线程grep263 tgrep的说明示例A–1tgrep程序的源代码(续)search_q=work;pthread_cond_signal(&search_q_cv);pthread_mutex_unlock(&search_q_lk);DP(DLEVEL2,("Sentworktosearchpoolthread "));}else{pthread_mutex_unlock(&search_q_lk);err=pthread_create(&tid,&detached_attr,search_thr,(void*)work);pthread_setconcurrency_np(pthread_getconcurrency_np()+1);DP(DLEVEL2,("Sentworktonewsearchthread "));if(flags&FS_STATS){pthread_mutex_lock(&stat_lk);st_search++;pthread_mutex_unlock(&stat_lk);}}break;default:fprintf(stderr,"tgrep:Internalerror,work_t->tpnotvalid ");exit(1);}if(err){/*NEEDTOFIXTHISCODE.Exitingisjustwrong*/264多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)fprintf(stderr,"Couldnotcreatenewthread! ");exit(1);}}OUT:if(flags&TG_PROGRESS){if(progress)fprintf(stderr,". ");elsefprintf(stderr," ");}/*wearedone,printthestuff.Allotherthreadsareparked*/if(flags&FC_COUNT){pthread_mutex_lock(&global_count_lk);printf("%d ",global_count);pthread_mutex_unlock(&global_count_lk);}if(flags&FS_STATS)prnt_stats();return(0);/*shouldhaveareturnfrommain*/}附录A•样例应用程序:多线程grep265 tgrep的说明示例A–1tgrep程序的源代码(续)/**Add_Work:Calledfromthemainthread,andcascadethreadstoaddfile*anddirectorynamestotheworkQ.*/intadd_work(char*path,inttp){work_t*wt,*ww,*wp;if((wt=(work_t*)malloc(sizeof(work_t)))==NULL)gotoERROR;if((wt->path=(char*)malloc(strlen(path)+1))==NULL)gotoERROR;strcpy(wt->path,path);wt->tp=tp;wt->next=NULL;if(flags&FS_STATS){pthread_mutex_lock(&stat_lk);if(wt->tp==DIRT)st_dir_search++;elsest_file_search++;266多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)pthread_mutex_unlock(&stat_lk);}pthread_mutex_lock(&work_q_lk);work_cnt++;wt->next=work_q;work_q=wt;pthread_cond_signal(&work_q_cv);pthread_mutex_unlock(&work_q_lk);return(0);ERROR:if(!(flags&FS_NOERROR))fprintf(stderr,"tgrep:Couldnotadd%stoworkqueue.Ignored ",path);return(-1);}/**Searchthread:Startedbythemainthreadwhenafilenameisfound*ontheworkQtobeserached.Ifalltheneededresourcesareready*anewsearchthreadwillbecreated.*/void*search_thr(void*arg)/*work_t*arg*/附录A•样例应用程序:多线程grep267 tgrep的说明示例A–1tgrep程序的源代码(续){FILE*fin;charfin_buf[(BUFSIZ*4)];/*4Kbytes*/work_t*wt,std;intline_count;charrline[128];charcline[128];char*line;registerchar*p,*pp;intpm_len;intlen=0;longbyte_count;longnext_line;intshow_line;/*forthe-voption*/registerintslen,plen,i;out_t*out=NULL;/*thisthreadsoutputlist*/pthread_yield_np();wt=(work_t*)arg;/*firstpass,wtispassedtouse.*//*len=strlen(string);*//*onlysetonfirstpass*/while(1){/*reusethesearchthreads*/268多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)/*initallbacktozero*/line_count=0;byte_count=0l;next_line=0l;show_line=0;pthread_mutex_lock(&running_lk);running++;pthread_mutex_unlock(&running_lk);pthread_mutex_lock(&work_q_lk);tglimit--;pthread_mutex_unlock(&work_q_lk);DP(DLEVEL5,("searchingfile(STDIO)%s ",wt->path));if((fin=fopen(wt->path,"r"))==NULL){if(!(flags&FS_NOERROR)){fprintf(stderr,"tgrep:%s.File"%s"notsearched. ",strerror(errno),wt->path);}gotoERROR;}setvbuf(fin,fin_buf,_IOFBF,(BUFSIZ*4));/*XXX*/DP(DLEVEL5,("Searchthreadhasopenedfile%s ",wt->path));附录A•样例应用程序:多线程grep269 tgrep的说明示例A–1tgrep程序的源代码(续)while((fgets(rline,127,fin))!=NULL){if(flags&FS_STATS){pthread_mutex_lock(&stat_lk);st_line_search++;pthread_mutex_unlock(&stat_lk);}slen=strlen(rline);next_line+=slen;line_count++;if(rline[slen-1]==’ ’)rline[slen-1]=’’;/***Iftheuncaseflagisset,copythereadinline(rline)**Totheuncaseline(cline)Setthelinepointertopointat**cline.**IfthecaseflagisNOTset,thenpointlineatrline.**lineiswhatiscompared,rlineiswhatisprintedona**match.*/if(flags&FI_IGNCASE){strcpy(cline,rline);uncase(cline);line=cline;270多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)}else{line=rline;}show_line=1;/*assumenomatch,if-vset*//*Theoldcoderemoved*/if(use_pmatch){for(i=0;ipath));free(wt->path);free(wt);notrun();附录A•样例应用程序:多线程grep273 tgrep的说明示例A–1tgrep程序的源代码(续)pthread_mutex_lock(&search_q_lk);if(search_pool_cnt>search_thr_limit){pthread_mutex_unlock(&search_q_lk);DP(DLEVEL5,("Searchthreadexiting "));if(flags&FS_STATS){pthread_mutex_lock(&stat_lk);st_destroy++;pthread_mutex_unlock(&stat_lk);}return(0);}else{search_pool_cnt++;while(!search_q)pthread_cond_wait(&search_q_cv,&search_q_lk);search_pool_cnt--;wt=search_q;/*wehaveworktodo!*/if(search_q->next)search_q=search_q->next;elsesearch_q=NULL;pthread_mutex_unlock(&search_q_lk);}274多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)}/*NOTREACHED*/}/**Continueline:Specialcasesearchwiththe-Cflagset.Ifyouare*searchingfileslikeMakefiles,somelinesmighthaveescapechar’sto*continethelineonthenextline.Sothetargetstringcanbefound,but*nodataisdisplayed.Thisfunctioncontinuestoprinttheescapedline*untiltherearenomore""charsfound.*/intcontinue_line(char*rline,FILE*fin,out_t*out,work_t*wt,int*lc,long*bc){intlen;intcnt=0;char*line;charnline[128];if(!(flags&FC_LINE))return(0);附录A•样例应用程序:多线程grep275 tgrep的说明示例A–1tgrep程序的源代码(续)line=rline;AGAIN:len=strlen(line);if(line[len-1]==’\’){if((fgets(nline,127,fin))==NULL){return(cnt);}line=nline;len=strlen(line);if(line[len-1]==’ ’)line[len-1]=’’;*bc=*bc+len;*lc++;add_output_local(&out,wt,*lc,*bc,line);cnt++;gotoAGAIN;}return(cnt);}/**cascade:Thisthreadisstartedbythemainthreadwhendirectorynames*arefoundontheworkQ.Thethreadreadsallthenewfile,anddirectory276多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)*namesfromthedirectoryitwasstartedwhenandaddsthenamestothe*workQ.(itfindsmorework!)*/void*cascade(void*arg)/*work_t*arg*/{charfullpath[1025];intrestart_cnt=10;DIR*dp;chardir_buf[sizeof(structdirent)+PATH_MAX];structdirent*dent=(structdirent*)dir_buf;structstatsbuf;char*fpath;work_t*wt;intfl=0,dl=0;intpm_file_len=0;pthread_yield_np();/*trytoigivecontrolbacktomainthread*/wt=(work_t*)arg;while(1){附录A•样例应用程序:多线程grep277 tgrep的说明示例A–1tgrep程序的源代码(续)fl=0;dl=0;restart_cnt=10;pm_file_len=0;pthread_mutex_lock(&running_lk);running++;pthread_mutex_unlock(&running_lk);pthread_mutex_lock(&work_q_lk);tglimit--;pthread_mutex_unlock(&work_q_lk);if(!wt){if(!(flags&FS_NOERROR))fprintf(stderr,"tgrep:Badworknodepassedtocascade ");gotoDONE;}fpath=(char*)wt->path;if(!fpath){if(!(flags&FS_NOERROR))fprintf(stderr,"tgrep:Badpathnamepassedtocascade ");gotoDONE;}278多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)DP(DLEVEL3,("Cascadingon%s ",fpath));if((dp=opendir(fpath))==NULL){if(!(flags&FS_NOERROR))fprintf(stderr,"tgrep:Can’topendir%s,%s.Ignored. ",fpath,strerror(errno));gotoDONE;}while((readdir_r(dp,dent))!=NULL){restart_cnt=10;/*onlytrytorestarttheinterupted10X*/if(dent->d_name[0]==’.’){if(dent->d_name[1]==’.’&&dent->d_name[2]==’’)continue;if(dent->d_name[1]==’’)continue;}fl=strlen(fpath);dl=strlen(dent->d_name);if((fl+1+dl)>1024){fprintf(stderr,"tgrep:Path%s/%sistoolong.""MaxPath=1024 ",fpath,dent->d_name);附录A•样例应用程序:多线程grep279 tgrep的说明示例A–1tgrep程序的源代码(续)continue;/*trythenextnameinthisdirectory*/}strcpy(fullpath,fpath);strcat(fullpath,"/");strcat(fullpath,dent->d_name);RESTART_STAT:if(stat(fullpath,&sbuf)){if(errno==EINTR){if(--restart_cnt)gotoRESTART_STAT;}if(!(flags&FS_NOERROR))fprintf(stderr,"tgrep:Can’tstatfile/dir%s,%s.""Ignored. ",fullpath,strerror(errno));gotoERROR;}switch(sbuf.st_mode&S_IFMT){caseS_IFREG:if(flags&TG_FILEPAT){if(pmatch(pm_file_pat,dent->d_name,&pm_file_len)){280多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)DP(DLEVEL3,("filepatmatch(cascade)%s ",dent->d_name));add_work(fullpath,FILET);}}else{add_work(fullpath,FILET);DP(DLEVEL3,("cascadeaddedfile(MATCH)%stoWorkQ ",fullpath));}break;caseS_IFDIR:DP(DLEVEL3,("cascadeaddeddir%stoWorkQ ",fullpath));add_work(fullpath,DIRT);break;}}ERROR:closedir(dp);DONE:附录A•样例应用程序:多线程grep281 tgrep的说明示例A–1tgrep程序的源代码(续)free(wt->path);free(wt);notrun();pthread_mutex_lock(&cascade_q_lk);if(cascade_pool_cnt>cascade_thr_limit){pthread_mutex_unlock(&cascade_q_lk);DP(DLEVEL5,("Cascadethreadexiting "));if(flags&FS_STATS){pthread_mutex_lock(&stat_lk);st_cascade_destroy++;pthread_mutex_unlock(&stat_lk);}return(0);/*pthread_exit*/}else{DP(DLEVEL5,("Cascadethreadwaitinginpool "));cascade_pool_cnt++;while(!cascade_q)pthread_cond_wait(&cascade_q_cv,&cascade_q_lk);cascade_pool_cnt--;wt=cascade_q;/*wehaveworktodo!*/if(cascade_q->next)cascade_q=cascade_q->next;282多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)elsecascade_q=NULL;pthread_mutex_unlock(&cascade_q_lk);}}/*NOTREACHED*/}/**PrintLocalOutput:Calledbythesearchthreadafteritisdonesearching*asinglefile.Ifanyoputputwassaved(matchinglines),thelinesare*displayedasagrouponstdout.*/intprint_local_output(out_t*out,work_t*wt){out_t*pp,*op;intout_count=0;intprinted=0;pp=out;pthread_mutex_lock(&output_print_lk);if(pp&&(flags&TG_PROGRESS)){附录A•样例应用程序:多线程grep283 tgrep的说明示例A–1tgrep程序的源代码(续)progress++;if(progress>=progress_offset){progress=0;fprintf(stderr,".");}}while(pp){out_count++;if(!(flags&FC_COUNT)){if(flags&FL_NAMEONLY){/*PintnameONLY!*/if(!printed){printed=1;printf("%s ",wt->path);}}else{/*Weareprintingmorethenjustthename*/if(!(flags&FH_HOLDNAME))printf("%s:",wt->path);if(flags&FB_BLOCK)printf("%ld:",pp->byte_count/512+1);if(flags&FN_NUMBER)printf("%d:",pp->line_count);printf("%s ",pp->line);284多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)}}op=pp;pp=pp->next;/*freethenodesaswegodownthelist*/free(op->line);free(op);}pthread_mutex_unlock(&output_print_lk);pthread_mutex_lock(&global_count_lk);global_count+=out_count;pthread_mutex_unlock(&global_count_lk);return(0);}/**addoutputlocal:iscalledbyasearchthreadasitfindsmatchinglines.*thematchingline,itsbyteoffset,linecount,etc.arestoreduntilthe*searchthreadisdonesearchingthefile,thenthelinesareprintedas*agroup.Thiswaythelinesfrommorethenasinglefilearenotmixed*together.*/附录A•样例应用程序:多线程grep285 tgrep的说明示例A–1tgrep程序的源代码(续)intadd_output_local(out_t**out,work_t*wt,intlc,longbc,char*line){out_t*ot,*oo,*op;if((ot=(out_t*)malloc(sizeof(out_t)))==NULL)gotoERROR;if((ot->line=(char*)malloc(strlen(line)+1))==NULL)gotoERROR;strcpy(ot->line,line);ot->line_count=lc;ot->byte_count=bc;if(!*out){*out=ot;ot->next=NULL;return(0);}/*appendtotheENDofthelist;keepthingssorted!*/op=oo=*out;while(oo){286多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)op=oo;oo=oo->next;}op->next=ot;ot->next=NULL;return(0);ERROR:if(!(flags&FS_NOERROR))fprintf(stderr,"tgrep:Outputlost.Nospace.""[%s:line%dbyte%dmatch:%s ",wt->path,lc,bc,line);return(1);}/**printstats:Ifthe-Sflagisset,afterALLfileshavebeensearched,*mainthreadcallsthisfunctiontoprintthestatsitkeepsonhowthe*searchwent.*/voidprnt_stats(void)附录A•样例应用程序:多线程grep287 tgrep的说明示例A–1tgrep程序的源代码(续){floata,b,c;floatt=0.0;time_tst_end=0;chartl[80];st_end=time(NULL);/*stoptheclock*/printf(" -----------------TgrepStats.-------------------- ");printf("Numberofdirectoriessearched:%d ",st_dir_search);printf("Numberoffilessearched:%d ",st_file_search);c=(float)(st_dir_search+st_file_search)/(float)(st_end-st_start);printf("Dir/filespersecond:%3.2f ",c);printf("Numberoflinessearched:%d ",st_line_search);printf("Numberofmatchinglinestotarget:%d ",global_count);printf("Numberofcascadethreadscreated:%d ",st_cascade);printf("Numberofcascadethreadsfrompool:%d ",st_cascade_pool);a=st_cascade_pool;b=st_dir_search;printf("Cascadethreadpoolhitrate:%3.2f%% ",((a/b)*100));printf("Cascadepooloverallsize:%d ",cascade_pool_cnt);printf("Numberofsearchthreadscreated:%d ",st_search);printf("Numberofsearchthreadsfrompool:%d ",st_pool);a=st_pool;b=st_file_search;288多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)printf("Searchthreadpoolhitrate:%3.2f%% ",((a/b)*100));printf("Searchpooloverallsize:%d ",search_pool_cnt);printf("Searchpoolsizelimit:%d ",search_thr_limit);printf("Numberofsearchthreadsdestroyed:%d ",st_destroy);printf("Max#ofthreadsrunningconcurrenly:%d ",st_maxrun);printf("Totalruntime,inseconds.%d ",(st_end-st_start));/*Whydidwewait?*/a=st_workfds;b=st_dir_search+st_file_search;c=(a/b)*100;t+=c;printf("WorkstoppedduetonoFD’s:(%.3d)%dTimes,%3.2f%% ",search_thr_limit,st_workfds,c);a=st_worknull;b=st_dir_search+st_file_search;c=(a/b)*100;t+=c;printf("WorkstoppedduetonoworkonQ:%dTimes,%3.2f%% ",st_worknull,c);if(tglimit==UNLIMITED)strcpy(tl,"Unlimited");elsesprintf(tl,"%.3d",tglimit);a=st_worklimit;b=st_dir_search+st_file_search;附录A•样例应用程序:多线程grep289 tgrep的说明示例A–1tgrep程序的源代码(续)c=(a/b)*100;t+=c;printf("WorkstoppedduetoTGLIMIT:(%.9s)%dTimes,%3.2f%% ",tl,st_worklimit,c);printf("Workcontinuedtobehandedout:%3.2f%% ",100.00-t);printf("---------------------------------------------------- ");}/**notrunning:Agluefunctiontotrackifanysearchthreadsorcascade*threadsarerunning.Whenthecountiszero,andtheworkQisNULL,*wecansafelysay,WEAREDONE.*/voidnotrun(void){pthread_mutex_lock(&work_q_lk);work_cnt--;tglimit++;current_open_files++;pthread_mutex_lock(&running_lk);if(flags&FS_STATS){pthread_mutex_lock(&stat_lk);if(running>st_maxrun){st_maxrun=running;290多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)DP(DLEVEL6,("MaxRunninghasincreasedto%d ",st_maxrun));}pthread_mutex_unlock(&stat_lk);}running--;if(work_cnt==0&&running==0){all_done=1;DP(DLEVEL6,("SettingALL_DONEflagtoTRUE. "));}pthread_mutex_unlock(&running_lk);pthread_cond_signal(&work_q_cv);pthread_mutex_unlock(&work_q_lk);}/**uncase:Agluefunction.Ifthe-i(caseinsensitive)flagisset,the*targetstrngandthereadinlineisconvertedtolowercasebefore*comparingthem.*/voiduncase(char*s){char*p;附录A•样例应用程序:多线程grep291 tgrep的说明示例A–1tgrep程序的源代码(续)for(p=s;*p!=NULL;p++)*p=(char)tolower(*p);}/**usage:Havetohaveoneofthese.*/voidusage(void){fprintf(stderr,"usage:tgreppattern<{file,dir}>... ");fprintf(stderr," ");fprintf(stderr,"Where: ");#ifdefDEBUGfprintf(stderr,"Debug-d=debuglevel-d(-d0forusage) ");fprintf(stderr,"Debug-f=blockfd’sfromuse(-f#) ");#endiffprintf(stderr,"-b=showblockcount(512byteblock) ");fprintf(stderr,"-c=printonlyalinecount ");fprintf(stderr,"-h=DoNOTprintfilenames ");fprintf(stderr,"-i=caseinsensitive ");292多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)fprintf(stderr,"-l=printfilenameonly ");fprintf(stderr,"-n=printthelinenumberwiththeline ");fprintf(stderr,"-s=Suppresserrormessages ");fprintf(stderr,"-v=printallbutmatchinglines ");#ifdefNOT_IMPfprintf(stderr,"-w=searchfora"word" ");#endiffprintf(stderr,"-r=Donotsearchforfilesinall""sub-directories ");fprintf(stderr,"-C=showcontinuedlines("\") ");fprintf(stderr,"-p=Filenameregexppattern.(Quoteit) ");fprintf(stderr,"-P=showprogress.-P1printsaDOTonstderr ""foreachfileitfinds,-P10printsaDOT ""onstderrforeach10filesitfinds,etc... ");fprintf(stderr,"-e=expressionsearch.(regexp)Morethenone ");fprintf(stderr,"-B=limitthenumberofthreadstoTGLIMIT ");fprintf(stderr,"-S=Printthreadstatswhendone. ");fprintf(stderr,"-Z=Printhelpontheregexpused. ");fprintf(stderr," ");fprintf(stderr,"Notes: ");fprintf(stderr,"Ifyoustarttgrepwithonlyadirectoryname ");fprintf(stderr,"andnofilenames,youmustnothavethe-roption ");fprintf(stderr,"setoryouwillgetnooutput. ");附录A•样例应用程序:多线程grep293 tgrep的说明示例A–1tgrep程序的源代码(续)fprintf(stderr,"Tosearchstdin(pipedinput),youmustset-r ");fprintf(stderr,"TgrepwillsearchALLfilesinALL ");fprintf(stderr,"sub-directories.(like*/**/*/**/*/*/*etc..) ");fprintf(stderr,"ifyousupplyadirectoryname. ");fprintf(stderr,"Ifyoudonotsupplyafile,ordirectoryname, ");fprintf(stderr,"andthe-roptionisnotset,thecurrent ");fprintf(stderr,"directory"."willbeused. ");fprintf(stderr,"Alltheotheroptionsshouldwork"like"grep ");fprintf(stderr,"The-ppattenisregexp;tgrepwillsearchonly ");fprintf(stderr," ");fprintf(stderr,"CopyRightByRonWinacott,1993-1995. ");fprintf(stderr," ");exit(0);}/**regexpusage:Telltheworldabouttgrepcustom(THREADSAFE)regexp!*/intregexp_usage(void){fprintf(stderr,"usage:tgrep-e"pattern"<-e...>""<{file,dir}>... ");294多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)fprintf(stderr," ");fprintf(stderr,"metachars: ");fprintf(stderr,".-matchanycharacter ");fprintf(stderr,"*-match0ormoreoccurrencesofpreviouschar ");fprintf(stderr,"+-match1ormoreoccurrencesofpreviouschar. ");fprintf(stderr,"^-matchatbeginningofstring ");fprintf(stderr,"$-matchendofstring ");fprintf(stderr,"[-startofcharacterclass ");fprintf(stderr,"]-endofcharacterclass ");fprintf(stderr,"(-startofanewpattern ");fprintf(stderr,")-endofanewpattern ");fprintf(stderr,"@(n)c-matchatcolumn ");fprintf(stderr,"|-matcheitherpattern ");fprintf(stderr,"\-escapeanyspecialcharacters ");fprintf(stderr,"\c-escapeanyspecialcharacters ");fprintf(stderr,"\o-turnonanyspecialcharacters ");fprintf(stderr," ");fprintf(stderr,"Tomatchtwodiffrerentpatternsinthesamecommand ");fprintf(stderr,"Usetheorfunction. ""ie:tgrep-e"(pat1)|(pat2)"file ""Thiswillmatchanylinewith"pat1"or"pat2"init. ");fprintf(stderr,"Youcanalsouseupto%d-eexpressions ",MAXREGEXP);fprintf(stderr,"RegExpPatternmatchingbroughttoyoubyMarcStaveley ");附录A•样例应用程序:多线程grep295 tgrep的说明示例A–1tgrep程序的源代码(续)exit(0);}/**debugusage:Ifcompiledwith-DDEBUG,turniton,andtelltheworld*howtogettgreptoprintdebuginfoondifferentthreads.*/#ifdefDEBUGvoiddebug_usage(void){inti=0;fprintf(stderr,"DEBUGusageandlevels: ");fprintf(stderr,"-------------------------------------------------- ");fprintf(stderr,"Levelcode ");fprintf(stderr,"-------------------------------------------------- ");fprintf(stderr,"0Thismessage. ");for(i=0;i<9;i++){fprintf(stderr,"%d%s ",i+1,debug_set[i].name);}fprintf(stderr,"-------------------------------------------------- ");296多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)fprintf(stderr,"Youcanorthelevelstogetherlike-d134forlevels ");fprintf(stderr,"1and3and4. ");fprintf(stderr," ");exit(0);}#endif/*PthreadsNPfunctions*/#ifdef__sunvoidpthread_setconcurrency_np(intcon){thr_setconcurrency(con);}intpthread_getconcurrency_np(void){return(thr_getconcurrency());}void附录A•样例应用程序:多线程grep297 tgrep的说明示例A–1tgrep程序的源代码(续)pthread_yield_np(void){/*InSolaris2.4,thesefunctionsalwaysreturn-1andseterrnotoENOSYS*/if(sched_yield())/*callUIinterfaceifweareolderthan2.5*/thr_yield();}#elsevoidpthread_setconcurrency_np(intcon){return;}intpthread_getconcurrency_np(void){return(0);}voidpthread_yield_np(void){298多线程编程指南•2006年10月 tgrep的说明示例A–1tgrep程序的源代码(续)return;}#endif附录A•样例应用程序:多线程grep299 300 附录BBSolaris线程示例:barrier.cbarrier.c程序演示了Solaris线程的屏障实现。有关屏障的定义,请参见第241页中的“在共享内存并行计算机上并行化循环”。示例B–1Solaris线程示例:barrier.c#define_REENTRANT/*IncludeFiles*/#include#include/*Constants&Macros*/*DataDeclarations*/typedefstruct{intmaxcnt;/*maximumnumberofrunners*/struct_sb{301 Solaris线程示例:barrier.c示例B–1Solaris线程示例:barrier.c(续)cond_twait_cv;/*cvforwaitersatbarrier*/mutex_twait_lk;/*mutexforwaitersatbarrier*/intrunners;/*numberofrunningthreads*/}sb[2];struct_sb*sbp;/*currentsub-barrier*/}barrier_t;/**barrier_init-initializeabarriervariable.**/intbarrier_init(barrier_t*bp,intcount,inttype,void*arg){intn;inti;if(count<1)return(EINVAL);302多线程编程指南•2006年10月 Solaris线程示例:barrier.c示例B–1Solaris线程示例:barrier.c(续)bp->maxcnt=count;bp->sbp=&bp->sb[0];for(i=0;i<2;++i){#ifdefined(__cplusplus)structbarrier_t::_sb*sbp=&(bp->sb[i]);#elsestruct_sb*sbp=&(bp->sb[i]);#endifsbp->runners=count;if(n=mutex_init(&sbp->wait_lk,type,arg))return(n);if(n=cond_init(&sbp->wait_cv,type,arg))return(n);}return(0);}/**barrier_wait-waitatabarrierforeveryonetoarrive.*附录B•Solaris线程示例:barrier.c303 Solaris线程示例:barrier.c示例B–1Solaris线程示例:barrier.c(续)*/intbarrier_wait(registerbarrier_t*bp){#ifdefined(__cplusplus)registerstructbarrier_t::_sb*sbp=bp->sbp;#elseregisterstruct_sb*sbp=bp->sbp;#endifmutex_lock(&sbp->wait_lk);if(sbp->runners==1){/*lastthreadtoreachbarrier*/if(bp->maxcnt!=1){/*resetrunnercountandswitchsub-barriers*/sbp->runners=bp->maxcnt;bp->sbp=(bp->sbp==&bp->sb[0])?&bp->sb[1]:&bp->sb[0];/*wakeupthewaiters*/cond_broadcast(&sbp->wait_cv);}}else{sbp->runners--;/*onelessrunner*/304多线程编程指南•2006年10月 Solaris线程示例:barrier.c示例B–1Solaris线程示例:barrier.c(续)while(sbp->runners!=bp->maxcnt)cond_wait(&sbp->wait_cv,&sbp->wait_lk);}mutex_unlock(&sbp->wait_lk);return(0);}/**barrier_destroy-destroyabarriervariable.**/intbarrier_destroy(barrier_t*bp){intn;inti;for(i=0;i<2;++i){if(n=cond_destroy(&bp->sb[i].wait_cv))return(n);附录B•Solaris线程示例:barrier.c305 Solaris线程示例:barrier.c示例B–1Solaris线程示例:barrier.c(续)if(n=mutex_destroy(&bp->sb[i].wait_lk))return(n);}return(0);}#defineNTHR4#defineNCOMPUTATION2#defineNITER1000#defineNSQRT1000void*compute(barrier_t*ba){intcount=NCOMPUTATION;while(count--){barrier_wait(ba);/*doparallelcomputation*/}306多线程编程指南•2006年10月 Solaris线程示例:barrier.c示例B–1Solaris线程示例:barrier.c(续)}main(intargc,char*argv[]){inti;intniter;intnthr;barrier_tba;doubleet;thread_t*tid;switch(argc){default:case3:niter=atoi(argv[1]);nthr=atoi(argv[2]);break;case2:niter=atoi(argv[1]);nthr=NTHR;break;case1:niter=NITER;nthr=NTHR;break;附录B•Solaris线程示例:barrier.c307 Solaris线程示例:barrier.c示例B–1Solaris线程示例:barrier.c(续)}barrier_init(&ba,nthr+1,USYNC_THREAD,NULL);tid=(thread_t*)calloc(nthr,sizeof(thread_t));for(i=0;i

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
关闭