Threading Programming Guide 1 About Threaded Programming

iOS线程编程手册-1-关于线程编程

虽然有GCD和NSOperation可以高效地实现多线程,但是你也可以使用OS X和iOS提供的底层的线程API。

文档结构

  • About Threaded Programming 介绍线程的概念和在app中的作用
  • Thread Management 介绍OS X中的线程技术和使用方法
  • Run Loops 介绍如何使用响应事件的Run Loop
  • Synchronization 线程同步和操作方式
  • Thread Safety Summary 线程安全相关

About Threaded Programming

随着计算机硬件的发展,单核心处理器性能遇到瓶颈,发展模式由垂直发展转向横向扩展。处理器被塞进越来越多的核心(core/unit),特别是图形处理器。近些年来,移动设备、智能可穿戴、路由器等嵌入式处理器也踏入了多核心处理器时代。

multicoremodel

系统方面,OS X和iOS系统支持多进程多任务,可用很好的利用这些核心来提高程序效率。

软件方面,线程的存在让一个应用程序也能利用多核心处理器的优势来提高自身运行效率。

操作系统在管理每一个应用程序的同时,也就是在管理着其内部的线程(包括主线程和其他线程),根据当前负载情况给它们制定运行计划。

技术上来说,线程是内核层面和应用层面数据的结合体。内核层面的数据用来协调到达线程的事件和安排执行。应用层面的数据包含具体任务执行代码、操作数据还有线程管理代码。

应用程序多线程优势:

  1. 提高用户操作体验
  2. 在多核处理器上提高实时性能

Alternatives to Threads

技术 描述
Operation objects Operation object封装了任务和相关数据,自动管理线程。属于Cocoa框架,基于GCD实现。具体见《Concurrency Programming Guide》。
Grand Central Dispatch(GCD) GCD使用任务队列执行线程代码。具体见《Concurrency Programming Guide》。
Idle-time notifications 对于相对较短和优先级别低的任务,可以使用Idle time notification。Cocoa框架对应实现类NSNotificationQueue。具体见《Notification Programming Topics》。
Asynchronous function 操作系统API提供了很多线程的异步函数可以提供自动地并发功能。这些API使用操作系统的后台驻留程序来处理或者创建线程来运行任务,然后返回结果给你。
Timers 定时/计划任务 详见下方的Timer Sources
Separate processes 创建独立的进程。注意!不管你使用的框架是Cocoa、Core Foundation 还是 Core Data Framework,一旦使用folk创建了进程,一定要在调用folk之后紧跟着执行exec函数或者其他启动进程的方法,否则会有各种不确定因素出现导致出问题。

Threading Support 一些为线程运行提供服务的技术

Threading Packages 线程类型?

虽然系统底层是采用Mach threads,但是你很少会用到它。一般会用到的是POSIX API或其衍生类型。这些类型提供了所有Mach thread的特性,包括执行预判模型和让线程独立运行的线程计划功能。

可以在你的应用程序中使用的线程技术:

Technology 技术 Description 描述
Cocoa Threads Cocoa中得NSThread类实现了线程功能。Cocoa还提供了可以直接spawn线程的NSObject类 和 直接在正在运行的线程中运行代码的能力。详见下方 Using NSObject to Spawn a Thread
POSIX threads POSIX API提供一个基于C的线程接口。如果你不是在用Cocoa框架,这是你使用线程的最好选择。POSIX线程接口相对简单并且提供非凡的自由度让你可以自定义线程。详见 Using POSXIX Threads
Multiprocessing Services 同样是基于C的。建议尽量不要使用。用来给旧版本的OS X系统用的。

线程状态转换图

盗图:)

threadstat2

Run Loops

Run Loops用来异步触发的事件。一个线程的Run Loop包含了它需要监听的事件类型。事件到达时就唤醒线程执行事件类型对应的任务。木有事件发生的时候Run Loop会sleep thread。

并不是每一个线程都需要对应的Run Loop才能运行,但是Run Loop休眠线程的特性可以节省重新创建线程的时间。

创建一个Run Loop,你需要在创建线程之后把线程指针、监听事件集合和对应的处理代码(event handlers)传给Run Loop然后启动(run)即可。

OS X和iOS系统运行的应用程序会自动地给进程的主线程创建Run Loop。其他自己创建的线程则需要手动写了。

Synchonization Tools 线程同步工具

线程间共享数据的同步访问。

可以使用lockconditionsatomic operations等。

互斥锁mutex,见下图:

mutex

条件锁condition

条件锁可以用来保证线程按照指定的顺序运行。condition会像门卫一样放行符合条件的线程让它继续执行,block掉其他不符合条件的线程直到condition得到满足为止。

POSIX和Foundation框架都支持条件锁。

如果你使用NSOperation的话可以使用Operation的dependency特性实现这一功能。

假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition 实例来做到这一点。

原子操作 atomic operation

在多进程(线程)访问资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。原子操作(atomic operation)是不需要synchronized。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。1

需要硬件实现,但是维基说可以用其他锁机制软件实现2

Inter-thread Communication 内部线程通信

好的多线程设计应该最小化线程之间的通信,然而在有些时候,这是避免不了的事情。一个线程可能需要把任务执行进度报告给主线程,这个时候就需要一种线程之间的通信方法了。

下面表格包含了一些线程间通信的方法及其优缺点,操作简易程度由上至下递增。

Mechanism 机制 Description 说明
Direct messaging 直接消息 Cocoa提供了直接把object的一个方法直接放到一个现成thread里面去运行的方法。方法直接在目标thread环境中执行,所以就可以访问到thread数据了。详见后方的 Cocoa Perform Selector Sources
Global variables,shared memory and objects 全局变量、共享内存和objects 另一个简单的共享数据的方法。使用不当会导致竞争条件、顺坏数据和应用程序退出。
Conditions 条件锁 Condition是一个线程同步工具,由于其检查condition的特性,可以用来执行简单的数据检查,满足条件后启动线程。详见后方的 Using Conditions
Run loop sources Run loop sources中的custom cources可以创建应用程序级别的事件进而触发线程运行。因为它是事件驱动的,run loop会在没有事件到达的时候自动休眠你的线程节省系统资源。详见后方的 Run Loops
Ports and sockets 基于Port的通行方式可以更加精确地实现线程间的通信模型,而且还是一种非常可靠的技术。更加重要的是,Ports和Sockets还可以用来和外部对象通行,例如外部进程和系统服务。为了提高效率,ports是run loop实现的。详见后面的 Run Loops
Message queues 消息队列 先进先出的多线程服务,并没有上面的方法高效,虽然很简单。详见 《 Multiprocessing Services Programming Guide
Cocoa distributed objects Cocoa消息分发objects Distributed objects 是Cocoa框架中基于port通信机制实现的技术。虽然可以用来实现线程间的通行,但是这么干既浪费资源又增加了写代码的复杂度。不提倡这么用罗。 详见《 Distributed Objects Programming Topics 》。

Tips

实践是检验真理的唯一方法,使用各种技巧优化多线程时都应该测试优化前后的效果。

尽量避免手动创建和管理线程

Cocoa框架和Core Foundation框架提供了很多多线程工具,让你可以从线程管理中脱离出来专心写任务逻辑,请尽量不要手动创建和管理线程。详见《 _Concurrency Programming Guide _ 》

高效的利用线程

如果你还是自己创建和管理线程,请注意管理线程对系统资源的消耗并努力优化。

避免线程间共享数据

最好给各个线程copy一份运行时需要的数据。

race-condition

你应该尽量让多线程跑的像右图>_>

创建一个多线程的应用程序并不简单,即使你使用了很严谨的锁机制解决共享数据/线程间通信问题。

线程和用户界面

适当的把繁重的任务交给其他线程处理,让管理用户界面的主线程脱离出来快速地响应用户事件。

盗图*2:

threadanduserinterface

注意线程退出时的行为

在应用程序退出的时候,它需要等待所有non-detached的线程完成后才完全退出。而detached的线程会被很快地终止并自动回收资源,因为对于系统来说它是可选的。如果你的一个线程可能在应用程序退出的时候还在写数据到硬盘,请把它设置为non-detached防止数据丢失。

创建non-detached线程需要额外的代码,因为很多高阶线程技术不会默认创建joinable的线程。你可能需要使用POSIX API来创建线程。而且你还需要在主线程里面添加代码来在退出的时候join non-detached的线程。详见后方的 Setting the Detached State of a Thread

如果你在编写Cocoa应用程序,你也可以使用applicationShouldTerminate:委托方法来延迟应用程序的终止。目标线程在完成工作后要调用replyToApplicationShouldTerminate:方法回应。详见 NSApplication Class Reference

Handle Exceptions 处理异常事件

异常事件处理机制依赖于call stack在异常抛出的时候执行clean up。因为线程有自己的call stack,所以线程要对自己的异常抛出事件做处理。非主线程抛出的异常没有配处理的话,结果和主线程的异常事件没有被处理是一样的——整个进程被终止。你不能把一个uncaught exception抛出给其他的线程处理。

如果你需要把抛出异常的事件通知其他线程,你可以采用上面说的线程通信机制来完成,但是但是前提你还是需要catch那个exception先。

注意:Cocoa中的NSException是self-contained object,可以pass给其他线程,但是前提也是你catch到了。

这句不知道在说什么:

In some cases, an exception handler may be created for you automatically. For example, the @synchronized directive in Objective-C contains an implicit exception handler.

Terminate Your Threads Cleanly 干净利落的结束你的线程:)

虽然说有直接让你强行结束线程的方法,但是它只能作为你最坏的选择,最好还是让线程执行完你编写的逻辑代码。

在线程执行完自己的任务之前结束会导致资源回收出问题,例如分配的内存,打开的文件读写把柄。

详见后方的 Terminating a Thread

Thread Safety in Libraries 类库中的线程安全

在编写Library的时候,你必须假设调用的应用程序是支持多线程的或者是可以转换到支持多线程的。关键区总是需要使用锁机制来控制访问。

下面的对我好像没啥用了:

For library developers, it is unwise to create locks only when an application becomes multithreaded. If you need to lock your code at some point, create the lock object early in the use of your library, preferably in some sort of explicit call to initialize the library. Although you could also use a static library initialization function to create such locks, try to do so only when there is no other way. Execution of an initialization function adds to the time required to load your library and could adversely affect performance.

Note: Alwaysremembertobalancecallstolockandunlockamutexlockwithinyourlibrary.You should also remember to lock library data structures rather than rely on the calling code to provide a thread-safe environment.

If you are developing a Cocoa library, you can register as an observer for the NSWillBecomeMultiThreadedNotification if you want to be notified when the application becomes multithreaded. You should not rely on receiving this notification, though, as it might be dispatched before your library code is ever called.

标签:ios, object-c, thread, runloop, ios线程管理