有些程序是一条直线,起点到终点;有些程序是一个圆,不断循环,直到将它切断。直线的如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花一现那样;圆如操做系统,一直运行直到你关机。
一个运行着的程序就是一个进程或者叫作一个任务,一个进程至少包含一个线程,线程就是程序的执行流。Mac和iOS中的程序启动,建立好一个进程的同时,一个线程便开始运行,这个线程叫主线程。主线程在程序中的地位和其余线程不一样,它是其余线程最终的父线程,且全部界面的显示操做即AppKit或UIKit的操做必须在主线程进行。
系统中的每个进程都有本身独立的虚拟内存空间,而同一个进程中的多个线程则共用进程的内存空间。每建立一个新的线程,都须要一些内存(如每一个线程有本身的Stack空间)和消耗必定的CPU时间。另外当多个线程对同一个资源出现争夺的时候须要注意线程安全问题。 编程
建立一个新的线程就是给进程增长了一个执行流,执行流总得有要执行的代码吧,因此新建一个线程须要提供一个函数或者方法做为线程的入口。 安全
NSThread提供了建立线程的途径,还能够提供了检测当前线程是不是主线程的方法。 使用NSThread建立一个新的线程有两种方式: 网络
其实NSObject直接就加入了多线程的支持,容许对象的某个方法在后台运行。如: 多线程
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
因为Mac和iOS都是基于Darwin系统,Darwin系统的XUN内核,是基于Mach和BSD的,继承了BSD的POSIX接口,因此能够直接使用POSIX线程的相关接口来使用线程。 并发
建立线程的接口为 pthread_create,固然在建立以前能够经过相关函数设置好线程的属性。如下为POSIX线程使用简单的例子。 框架
// // main.c // pthread // // Created by Lu Kejin on 1/27/12. // Copyright (c) 2012 Taobao.com. All rights reserved. // #include <stdio.h> #include <pthread.h> #include <unistd.h> void *pthreadRoutine(void *); int main (int argc, const char * argv[]) { pthread_attr_t attr; pthread_t pthreadID; int returnVal; returnVal = pthread_attr_init(&attr); returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); int threadError = pthread_create(&pthreadID, &attr, &pthreadRoutine, NULL); returnVal = pthread_attr_destroy(&attr); if (threadError != 0) { // Report an error. } sleep(10); return 0; } void *pthreadRoutine(void *data){ int count = 0; while (1) { printf("count = %d\n",count++); sleep(1); } return NULL; }
不少时候咱们使用多线程,须要控制线程的并发数,毕竟线程也是消耗系统资源的,当程序中同时运行的线程过多时,系统必然变慢。 因此不少时候咱们会控制同时运行线程的数目。 异步
NSOperation能够封装咱们的操做,而后将建立好的NSOperation对象放到NSOperationQueue中,OperationQueue便开始启动新的线程去执行队列中的操做,OperationQueue的并发度是能够经过以下方式进行设置: async
- (void)setMaxConcurrentOperationCount:(NSInteger)count
GCD是Grand Central Dispatch的缩写,是一系列的BSD层面的接口,在Mac 10.6 和iOS4.0之后才引入的,且如今NSOperation和NSOperationQueue的多线程的实现就是基于GCD的。目前这个特性也被移植到FreeBSD上了,能够查看libdispatch这个开源项目。 ide
好比一个在UIImageView中显示一个比较大的图片 函数
dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(imageDownloadQueue, ^{ NSURL *imageURL = [NSURL URLWithString:@"http://test.com/test.png"]; NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; UIImage *image = [UIImage imageWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ [imageView setImage:image];//UIKit必须在主线程执行 }); });
固然,GCD除了处理多线程外还有不少很是好的功能,其创建在强大的kqueue之上,效率也可以获得保障。
线程间通讯和进程间通讯从本质上讲是类似的。线程间通讯就是在进程内的两个执行流之间进行数据的传递,就像两条并行的河流之间挖出了一道单向流动长沟,使得一条河流中的水能够流入另外一条河流,物质获得了传递。
1.performSelect On The Thread
框架为咱们提供了强制在某个线程中执行方法的途径,若是两个非主线程的线程须要相互间通讯,能够先将本身的当前线程对象注册到某个全局的对象中去,这样相互之间就能够获取对方的线程对象,而后就可使用下面的方法进行线程间的通讯了,因为主线程比较特殊,因此框架直接提供了在出线程执行的方法。
@interface NSObject (NSThreadPerformAdditions) - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array; - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; // equivalent to the first method with kCFRunLoopCommonModes - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0); - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0); // equivalent to the first method with kCFRunLoopCommonModes ... @end
2.Mach Port
在苹果的Thread Programming Guide的Run Pool一节的Configuring a Port-Based Input Source 这一段中就有使用Mach Port进行线程间通讯的例子。 其实质就是父线程建立一个NSMachPort对象,在建立子线程的时候以参数的方式将其传递给子线程,这样子线程中就能够向这个传过来的NSMachPort对象发送消息,若是想让父线程也能够向子线程发消息的话,那么子线程能够先向父线程发个特殊的消息,传过来的是本身建立的另外一个NSMachPort对象,这样父线程便持有了子线程建立的port对象了,能够向这个子线程的port对象发送消息了。
固然各自的port对象须要设置delegate以及schdule到本身所在线程的RunLoop中,这样来了消息以后,处理port消息的delegate方法会被调用,你就能够本身处理消息了。
RunLoop从字面上看是运行循环的意思,这一点也不错,它确实就是一个循环的概念,或者准确的说是线程中的循环。 本文一开始就提到有些程序是一个圈,这个圈本质上就是这里的所谓的RunLoop,就是一个循环,只是这个循环里加入不少特性。
首先循环体的开始须要检测是否有须要处理的事件,若是有则去处理,若是没有则进入睡眠以节省CPU时间。 因此重点即是这个须要处理的事件,在RunLoop中,须要处理的事件分两类,一种是输入源,一种是定时器,定时器好理解就是那些须要定时执行的操做,输入源分三类:performSelector源,基于端口(Mach port)的源,以及自定义的源。编程的时候能够添加本身的源。RunLoop还有一个观察者Observer的概念,能够往RunLoop中加入本身的观察者以便监控着RunLoop的运行过程,CFRunLoop.h中定义了全部观察者的类型:
enum CFRunLoopActivity { kCFRunLoopEntry = (1 << 0), kCFRunLoopBeforeTimers = (1 << 1), kCFRunLoopBeforeSources = (1 << 2), kCFRunLoopBeforeWaiting = (1 << 5), kCFRunLoopAfterWaiting = (1 << 6), kCFRunLoopExit = (1 << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU }; typedef enum CFRunLoopActivity CFRunLoopActivity;
若是你使用过select系统调用写过程序你即可以快速的理解runloop事件源的概念,本质上讲事件源的机制和select同样是一种多路复用IO的实现,在一个线程中咱们须要作的事情并不单一,如须要处理定时钟事件,须要处理用户的触控事件,须要接受网络远端发过来的数据,将这些须要作的事情通通注册到事件源中,每一次循环的开始便去检查这些事件源是否有须要处理的数据,有的话则去处理。 拿具体的应用举个例子,NSURLConnection网络数据请求,默认是异步的方式,其实现原理就是建立以后将其做为事件源加入到当前的RunLoop,而等待网络响应以及网络数据接受的过程则在一个新建立的独立的线程中完成,当这个线程处理到某个阶段的时候好比获得对方的响应或者接受完了网络数据以后便通知以前的线程去执行其相关的delegate方法。因此在Cocoa中常常看到scheduleInRunLoop:forMode:这样的方法,这个即是将其加入到事件源中,当检测到某个事件发生的时候,相关的delegate方法便被调用。对于CoreFoundation这一层而言,一般的模式是建立输入源,而后将输入源经过CFRunLoopAddSource函数加入到RunLoop中,相关事件发生后,相关的回调函数会被调用。如CFSocket的使用。 另外RunLoop中还有一个运行模式的概念,每个运行循环必然运行在某个模式下,而模式的存在是为了过滤事件源和观察者的,只有那些和当前RunLoop运行模式一致的事件源和观察者才会被激活。
每个线程都有其对应的RunLoop,可是默认非主线程的RunLoop是没有运行的,须要为RunLoop添加至少一个事件源,而后去run它。通常状况下咱们是没有必要去启用线程的RunLoop的,除非你在一个单独的线程中须要长久的检测某个事件。