线程编程指南

来源:互联网 发布:utorrent做种中 知乎 编辑:程序博客网 时间:2024/05/17 21:48

摘译自http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html%23//apple_ref/doc/uid/10000057i-CH6-SW2


线程编程指南之一:介绍

介绍

线程是在一个应用中同时执行多个代码路径的其中一个方法。尽管一些新技术如operation对象和Grand Central Dispatch(GCD)提供了一个更流行和高效的实现这种技术的架构,但Mac OS X和iOS也提供了创建和管理线程的接口。

这个文档介绍了Mac OS X上的线程包并提供了这个线程包的使用方法。这个文档也描述了在你的应用中支持线程和多线程代码同步的相关技术。

Important:如果你开发一个新的应用,建议你去研究Max OS X上用于实现并发的可供选择的技术。特别是当你不熟悉实现一个基于线程的应用程序所需要的设计技术。这些可供选择的技术相比传统的线程简化了你实现多路径并发执行的工作并提供了更好的性能。关于这些技术的信息,可以查看Concurrency Programming Guide.

文档的组织

这个文档包含下面的章节和附录:

“AboutThreaded Programming”:介绍了线程的概念及它们在应用设计中的角色。

“Thread Management” :提供了关于Max OSX中线程技术的信息以及如何使用它们。

“Run Loops” :提供了关于在次要的线程(即非主线程)中如何管理event-processing loops的信息。

“Synchronization” :描述了同步问题和工具,用于防止多线程环境数据冲突或者应用崩溃。

“Thread Safety Summary” :提供了一个关于MaxOS X、iOS和它们核心框架的固有的线程安全的高层面描述。

参见

关于线程可供选择的技术的信息,可以查看Concurrency Programming Guide.

这个文档只提供了关于POSIX API的一小部分使用方法。更多关于POSIX线程例程的信息可以查看pthread manpage。关于POSIX更深入的解释和用户,请看David R. Butenhof编写的Programmingwith POSIX Threads 。

   

 

线程编程指南之二:关于线程编程

关于线程编程

很多年来,计算机的最大性能被计算机的单核处理器的速度所限制。随着单个处理器的速度达到它们物理的上限,芯片制造商转换到多核设计,让计算机具备同时执行多个任务的能力。尽管Mac OS X利用这些芯片执行系统相关的任务,你的应用程序可以通过线程使用它们。

什么使线程?

    线程是在你的代码中实现多个代码执行路径的轻量级的方式。在系统层面,程序一起执行,系统根据每个程序和其它应用程序的需要分发执行时间。每个程序中,存在一个或多个执行线程被用于同时或几乎同时的方式去执行不同的任务。系统管理这些线程的执行,调度它们在可用的芯片上执行并且可以中断它们让其它的线程得以执行。

从技术的观点看,线程是一个内核层和应用层所需要的用于管理代码执行的数据结构的组合。内核层面的数据结构用于协调事件分发和在可用的芯片上进行基于优先级的线程调度。应用层面的数据结构包括用于存储函数调用的调用栈和应用程序需要的管理和操作线程属性和状态的结构。

在一个非并发的应用中,只有一个线程在执行。这个线程开始和结束于应用的main例程,一个接一个地执行实现应用所有行为的方法或函数。与之相对,支持并发的应用从一个线程开始执行然后在需要的时候创建更多的线程用于执行另外的执行路径。每个新的路径拥有它自己的自定义的开始例程,在应用的主线程之外单独的执行代码。在一个应用中使用多线程提供了两个重要的潜在优势:

l   多线程可以提高一个应用的相应速度。

l   多线程可以提高多核系统上一个应用实时性能。

如果你的应用只有一个线程,那么这个线程必须做所有的事情。它必须响应事件、更新应用窗口并且完成实现应用行为的所有操作。只有一个线程的问题是每次只能执行一个任务。所以当你的计算需要很长时间才能完成会发生什么呢?当你的代码忙于计算它需要的值,你的应用停止响应用户事件和更新窗口。如果这个行为足够长,用户可能认为你的应用挂起了,就会强制退出它。如果你把自定义的计算放到一个独立的线程上,这样你的应用的主线程将会空闲下来以一个更及时的方式去响应用户交互。

对于目前一般的多核计算机,针对一些类型的应用,线程提供了提升性能的方式。执行不同任务的线程能够同时的在不同的处理器核上执行,这样一个应用能够在一个给定的时间完成更多的工作。

当然,线程并不是解决应用性能问题的灵丹妙药。线程带来好处的同时也伴随一些潜在的问题。在应用中有多个执行路径会增加代码的复杂度。每个线程需要它和其它线程的动作用于防止毁坏掉应用的状态信息。因为在一个应用的线程共享相同的内存空间,它们需要存取所有相同的数据结构。如果两个线程同时操作相同的数据结构,一个线程可能会覆盖另一个线程的改变从而会毁掉作为结果的数据结构。即使做了合适的保护,你仍然需要小心编译器优化引入微妙的bugs到你的程序中。

线程术语

在深入讨论线程和它们支持的技术之前,有必要定义一些基本的术语。如果你熟悉Carbon’s Multiprocessor Services API或者UNIX系统, 你会发现术语“task”的使用方式不同。在MacOS的早期版本,术语”task”被用于区分使用Multiprocessor Services创建的线程和使用Carbon ThreadManager API创建的线程。在UNIX系统上,术语”task”仍然有时指运行过程。在实际应用中,一个Multiprocessor Services任务等价于一个基于优先级调度的线程。

Carbon Thread Manager和Multiprocessor Services APIs都是Mac OS X上的传统的技术,这个文档采用下面的术语:

l   术语thread指独立的代码执行路径。

l   术语process指一个可执行的,可以包含多个线程。

l   术语task指需要执行的工作的一个抽象概念。

线程的替代技术

自己创建线程的一个问题是它增加了代码的不确定性。线程是相对底层的、复杂的在应用中支持并发的方式。如果你没有完全理解你的设计选择的复杂性,你很容易遇到同步或时序问题,危害性小到导致微妙的行为改变,甚至会使应用崩溃、毁坏用户数据。

另外一个需要考虑的是,你是否需要线程或并发技术。线程解决如何在一个过程中并发的执行多个代码路径的问题。可能有些情况,你正在做的工作不需要并发。线程引入了大量的开销到你的过程中,不仅内存消耗还有处理器时间。你可能发现对于计划的工作这种消耗太大了,或者其它的选择更容易实现。

Table 1-1列出了一些线程的替代技术。这个表包括针对线程的替代技术(比如opration对象和GCD)和使用单线程的更有效的替代方案。

l  Operation对象:在Mac OS Xv10.5引入,一个operation对象是一个可被次线程执行的一个任务的封装。这个封装隐藏完成这个任务所需要的线程管理部分,让你专心于任务本身。典型的使用方式是和一个queue对象配合使用,queue对象管理operation对象在多个线程中执行。关于operation对象使用方式的更多信息,查看Concurrency Programming Guide.

l   Grand Central Dispatch(GCD):Mac OS x v10.6中引入,GrandCentral Dispatch是线程的另一个替代方案让你专注于任务的实现而不是线程的管理。使用GCD, 你定义你想要执行的任务然后把它添加到一个工作queue中,这个queue在一个合适的线程中处理你的任务。工作queue考虑可用的核的数量和执行你的任务的负载,比你使用threads更有效率。关于GCD如何使用的信息,查看Concurrency Programming Guide。

l  Idle-time notifications:对于执行时间短并且优先级低的任务,idle time notificatio让你能够在应用不忙时执行这个任务。Cocoa通过NSNotificationQueue提供对idle-time notification的支持。为了请求一个idle-time notification,使用NSPostWhenIdle选项向默认的NSNotificationQueue对象发送一个notification。这个队列延迟发送你的notification对象直到run loop变为空闲状态。更多信息,请查看:Notification Programming Topics.

l   Asynchronous functions:系统接口包括很多异步函数,提供了自动的并发。这些APIs可能使用系统守护线程和进程或者创建自定义的线程完成它们的工作并向你返回结果(真正的实现是不相关的因为它和你的代码是分开的)。当你设计应用时,查找提供异步行为的函数并且考虑使用它们而不是使用等价在一个自定义线程上执行的同步函数。

l    Timers:可以在应用的主线程中使用Timers执行周期性的不重要的任务,但这些任务仍然需要在周期的时间间隔中需要服务。关于timers的更多信息,请查看:“TimerSources.”

l  Separate processes: 尽管比线程重量级,当你的任务与应用毫无关系时创建一个独立的线程可能是有用的。如果一个任务需要大量的内存或者必须使用root用户权限执行,你可能需要创建一个进程。比如,你可能使用一个64位的服务器进程计算一个大数据使用你的32位系统向用户显示结果。

警告:当使用fork函数加载独立的进程,你必须在函数fork调用之后调用函数exec或者一个相似的函数。依赖于Core Foundation,Cocoa,或者Core Data框架(显式或隐式)的应用必须随后调用一个函数exec,否则这些框架可能会出现问题。

线程支持

如果你有使用线程的现存的代码,Mac OS X和iOS提供了在应用中创建线程的一些技术。另外,这两个系统都提供了管理和同步需要使用线程完成的工作的支持。下面的部分描述一些当在Mac OS X和iOS环境下使用线程需要注意的一些关键技术。

线程包

尽管线程的底层实现机制是Machthreads,你很少(或从不)在Mach level层面使用线程。作为代替,你通常使用更方便的POSIX API或它的一个派生物。Mach的实现没有提供所有线程的基本特性,然后包括抢占式的模式和调度线程的能力,所以它们相互独立。

  下面列出了你在应用中可以使用的线程技术。

Cocoathreads:Cocoa使用类NSThread实现线程。Cocoa也提供在类NSObject中提供了用于生成新线程和在已经运行的代码中执行线程的方法。更多信息,请看和 “UsingNSThread” and “UsingNSObject to Spawn a Thread.”

POSIX threads:POSIX提供了基于C的创建线程的接口。如果你正在写一个Cocoa程序,这是创建线程的最好的选择。POSIX接口相当的简单同时提供了非常便捷的方式配置线程。更多信息,请看 “UsingPOSIX Threads”。

MultiprocessingServices:这是一个传统的技术,并且只能在Mac OS X上使用,新的开发不应使用这个技术。如果需要关于这个技术的更多信息,kei查看 Multiprocessing Services Programming Guide.

一个线程开始执行后,他有3个状态:运行中、准备运行、阻塞。如果一个线程没有正在运行,它可能阻塞或等待输入或者它已经开始运行但是现在没有被调度。

如果你创建一个线程,你必须给线程指定一个入口函数。当这个函数返回或当你显式的终止线程,线程永远的停止并被系统回收。因为线程非常的耗资源和时间,所以建议你的入口函数做重要的事情或建立一个run loop以允许循环操作。

关于线程技术的更多信息以及如何使用它们,可以查看“ThreadManagement.”

Run Loops

一个 run loop是一个用于管理线程异步到来事件的结构一个run loop为线程监视一个或多个事件源当事件到来的时候,系统唤醒线程并且向run loop分发事件,接着run loop分发这些事件给你指定的句柄如果没有指定的事件需要处理,run loop使线程进入休眠状态

你不需要为你创建的线程使用run loop,但是这样做可以给给你的用户带来好的体验。Run loops使创建长时间存在的并使用很少资源的线程成为可能。因为一个runloop使它的线程进入休眠状态当没什么事情可做的时候,它消除了轮询,轮询非常耗费CPU周期并且阻止进入休眠状态、节省电量。

配置一个run loop,你需要做的就是加载你的线程、获得run loop对象的引用、安装事件句柄、告诉run loop运行。Cocoa和Carbon自动的处理了主线程run loop的配置。如果你打算创建一个长时间运行的次线程,你必须为创建的线程配置run loop。

关于run loops的细节和如何使用它们的例子可以查看“RunLoops.”。

同步工具

多线程编程的其中一个危险是多线程之间的资源冲突。如果多个线程同时想使用或修改相同的资源,问题就会发生。缓和这个问题的一个方式是消除共享资源,确保每个线程拥有它自己的要操作的数据集。维护完全独立的资源不是一个选择,你也许需要使用锁、条件、原子操作和其它的技术同步访问资源。

锁机制提供一个蛮力的方式保护代码同时只能被一个线程执行。最常用的锁为互斥锁。当一个线程尝试获取一个正在被别的线程拥有的锁时,这个线程会阻塞直到另外的线程释放这个锁。一些框架提供了对互斥锁的支持,尽管它们都是基于相同的底层技术。另外,Cocoa提供了一些互斥锁的变体用于支持不同的行为,比如递归锁。关于锁的类型的更多信息,请查看“Locks.”。

除了锁外,系统提供了对“条件”的支持,用于确保应用中任务以合适的序列执行。一个“条件”作为一个看门人的角色,它阻塞线程直到它表示的条件变为ture。当条件满足时,“条件”放开一个线程并允许它接着运行。POSIX层和Foundation框架都对“条件”提供了直接的支持。(如果你使用NSOperation,你可以配置你的operation对象的依赖用于序列化任务的运行,它的行为与“条件”提供的非常相像)

尽管锁和“条件”在并发设计中非常常用,原子操作是另外一种保护和同步数据存取的方式。原子操作提供了一个在标量类型上执行数学和逻辑操作的轻量级的可用选择的替代锁的机制。原子操作使用特殊的硬件指令确保变量在修改完成之前不会被另外的线程访问。

更多关于同步工具的信息,请查看“Synchronization Tools.”。

线程内通信

尽管一个好的设计最小化了通信的需求,在一些时候,线程之间的通信是必要的。(一个线程为你的程序工作,如果找个工作的结果不会被使用,它有什么好处呢?)线程也许需要处理新的工作请求或者通知应用的主线程通报它们的处理过程。在这些情况下,你需要一种方式从一个线程获取信息并传递给令一个线程。

有很多的方式用于线程间的通信,每种方式有其有点和缺点。“配置线程本地存储”列出了在Mac OS X可用的最常用的机制。(除了消息队列和Cocoa分布式对象不建议使用,其它的技术都可以用在iOS上)表中的技术以复杂度递增的方式列出。

Direct messaging:Cocoa应用支持在其它的线程上执行selectors。 这个能力意味着一个线程能够在另一个线程上执行一个方法。因为它们在目标线程的上下文中执行,以这种方式发送的消息自动在目标线程上序列化。更多关于input sources的信息,请查看see “Cocoa Perform Selector Sources.”。

Global variables, shared memory, and objects:另一种线程间通信的简单机制是使用全局变量、共享对象或共享内存。尽管共享对象快并且简单,它们比直接消息传递更脆弱。共享对象必须用锁或其它的同步机制保护,以确保程序的正确性。如果处理不慎可能导致竞争条件、毁坏的数据或者使应用崩溃。

Conditions:“条件”是一种你可以用于控制一块特定代码的访问的同步工具。你可以把“条件“看作看门人,只有条件状态满足时才让一个线程运行。更多关于如果使用”条件“的信息,请查看“UsingConditions.”

    Run loopsources:一个定制的run loop sources是你建立为线程接收应用特定消息的东西。因为它们是事件驱动的,当没有什么事情可做的时候,run loop sources使你的线程休眠,这样就提升了线程的效率。更多关于runloops和run loop sources的信息,请查看“Run Loops.”。

  Ports and sockets:基于端口的系统是一个线程间通信的更复杂的方式,但是它也是一个非常可靠的技术。更重要的是,端口和套接字可以用于和外部的实体通信,比如其它的进程或服务。为了效率,端口使用run loop sources实现,所以当没有数据在指定端口等待时线程会进入休眠状态。更多关于run loops和port-based input sources的信息,请查看“Run Loops.”。

Message queues:为了兼容而遗留的MultiprocessingService定义了一个先进先出的队列用于管理输入和输出的数据。尽管消息队列简单和便捷,但是它们没有其它几种通信方式效率高。更多关于如何使用消息队列的方法,请查看MultiprocessingServices Programming Guide。

Cocoa distributed objects:Distributed objects提供基于端口的高层的实现,尽管它可用于内部线程通信,非常不建议这样做因为它会引起很大的系统消耗。分布式对象用于同其它的进程通信更合适,因为进程运行的消耗已经很高了。更多信息,请查看Distributed Objects Programming Topics.。

  线程支持

下面的章节介绍一些实现代码线程安全的指导方针。一些指导方针提供了使代码性能更好的技巧。对于任何性能技巧,在你改动任何代码之前,你应该收集相关的性能统计数据。

避免显示的创建创建线程

手动的写创建线程的代码是乏味而且是容易出错的,所以你应该避免这么做。 Mac OS和iOS通过其它的APIs提供了隐式的并发。考虑使用异步APIs、GCD或者operation对象创建线程而不是自己写创建线程的代码。这些技术完成线程相关的操作并负责正确的管理它。另外,像GCD和operation对象的技术用于管理线程比你自己创建的代码更有效率,它们可以根据系统的负荷调整活动的线程数。更多关于GCD和operation对象的信息,可以查看:ConcurrencyProgramming Guide.。

使你的线程保持适度的忙碌

如果你决定手动的创建线程,记住线程消耗宝贵的系统资源。你需要尽力你设计的线程适度的处于活动状态和富有成效的。同时你需要害怕去中止消耗了系统大部分时间片的线程。线程使用一

内其他进程使用。

注意:

在你开始释放僵死线程之前,你应该记录一下应用现在的性能。杀死一些线程后,测量一下现在的性能,以验证你的观点。


原创粉丝点击