iOS多线程杂论

最近这段时间忙忘记了总结自己的所得以及所获了,由于为年后工作座准备今天看了看多线程,那么下面为大家带来我的所感及所得吧。

iOS多线程的分布

(1) NSThread

(2) NSOperation

(3) GCD


现在对下面三个进行一个个的分析,希望那里说得不对的地方希望简友们帮我指点一二。

1,NSThread

优点:NSThread相对比较轻量级
缺点:需要自己管理线程生命周期,线程同步,线程同步对数据加锁有一定的系统开销;


NSThread 实现的三种方式:

1,
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadInitDoSomething) object:nil]; [thread start];
这种方法需要手动开启线程。
2,
[NSThread detachNewThreadSelector:@selector(threadDetachDosomething) toTarget:self withObject:nil];
这种便利构造的方法不需要手动开启
3,
[self performSelectorInBackground:@selector(backGround) withObject:nil];
这种方式是NSObject对象自带的开启后台线程的方法。

2,NSOperation

优点:
不需要手动关系线程,可以把精力放在自己要执行的操作上面,NSOperation我们一般使用它的子类NSInvocationOperation NSBlockOperation或者继承NSOperation的自定义任务,我们经常将任务和队列NSOperationQueue进行搭配使用,一个对象一个任务,更利于任务的管理,还有一个优点在于可以明确的确定依赖关系
缺点:
他是一个OC对象,那么相对于C函数效率要低,而且基于GCD,那么GCD将提供比他更加全面的功能;


NSOperation 的使用:

1,
NSInvocationOperation *incocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationDoSomething) object:nil]; [incocation start];
这种方式利用的Target-Action的设计模式,让响应者去执行任务。
2,
NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%d, %@", [NSThread isMainThread], [NSThread currentThread]); }]; [block start];
这种方式利用OC里面经典的语法block(语法块)。但是和上者一样,如果单独使用NSOperation的子类对象必须手动的开启任务。
3,
//NSOperationQueue里面只有串行的时候线程优先级才是可行的 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //最大并发量,如果最大病发量为1时,那么队列里面的任务将串行,也就是执行完一个任务才能执行下一个,如果不为1,那就是并发进行。 queue.maxConcurrentOperationCount = 1;

//添加block块任务 NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"--0--%d, %@", [NSThread isMainThread], [NSThread currentThread]); }]; //设置任务的优先级,只有队列为串行的时候优先级才能起到绝对的作用 [block setQueuePriority:NSOperationQueuePriorityVeryHigh]; NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"--1--%d, %@", [NSThread isMainThread], [NSThread currentThread]); }]; [block1 setQueuePriority:NSOperationQueuePriorityNormal]; NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"--2--%d, %@", [NSThread isMainThread], [NSThread currentThread]); }]; [block2 setQueuePriority:NSOperationQueuePriorityVeryLow]; NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"--3--%d, %@", [NSThread isMainThread], [NSThread currentThread]); }]; [block3 setQueuePriority:NSOperationQueuePriorityVeryHigh];

//设置以来关系,只有执行完block之后才会去执行block1,这叫做任务block1依赖于block,在项目开发中经常用到
[block1 addDependency:block];
//添加任务,任务为NSOperation对象的子类,添加到任务队列中之后会自动去执行,不需要start;
[queue addOperation:block];
[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];`

NSOperationQueue在开发中经常会使用到,比如我们做多任务下载的时候,使用自定义NSOperation子类和NSOperationQueue结合使用,每个NSOperation对象是一个任务,而NSOperationQueue却完美的担任了任务关系器的角色。抽时间会把demo上传到github上面,请及大家及时关注。任务之间的依赖也是NSOperation的一大完美特征。

3,GCD(重点介绍)

GCD简介

GCD-(Grand Central Dispatch)是Apple公司开发的一种技术。这种技术可以在多核硬件中并发处理多个任务。也可以说,GCD是多线程的一种扩展和替代技术。GCD 在Mac OS x 10.6 和 IOS 4.0 以后开始支持。

GCD的特点

1.GCD可以在充分利用多核硬件并发处理多个任务。也就是说,效率高。
2.GCD 的API提供了很多同步,异步,分组等正对多任务,同步,异步的操作。也就是一种比多线程还要牛X写的技术。
3.使用GCD我们可以更容易的利用多核处理器并行处理任务。
4.GCD 使用后不用程序去管理线程的开闭,GCD会在系统层面上去动态检测系统状态,开闭线程。

我在学习GCD的时候掉进的误区

看了很多网上的博客,写的都特别好,但是我在使用的时候还是遇到了一些问题,比如同步和异步与串行和并行之间总是分不清,并行和并发我一直以为是一个概念之类的问题,现在我先对这两个问题点将我的理解分享给大家,


串行和并行与同步异步执行的问题,串行并行决定了对列中任务的执行规则(循序)串行队列队列里面的任务循序执行,并行队列根据系统资源而定,没有一定的规则。并且给我们的感觉是多个任务同时执行。异步和同步只是GCD里面对任务block的处理问题,同步忽略block代码循序执行,异步却是正常的block逻辑,及某一时刻进行回调,而且是先进队列咧线回调(在串行队列里)。


并行和并发,由于现在多核cpu的出现出现了并行这个说话,及一个cpu核心处理一个任务,两个任务或者多个任务同时执行,但是并发却是同一个cpu核心来回调度,但是因为时间极短的来回调度,给我们造成了错误的感觉两个任务同时执行,其实是执行完这个任务的某一部分之后将cpu调度到另一个任务。来回调度的一个过程

我心中的GCD

经过一些行业面试人员的反馈,GCD是多线程里面被问及最多的,所以在GCD里面我投入了更多的精力,下面我们一起走入我的GCD时间吧,在这里如果有做得不足的地方希望大家可以给我一些见解,共同进步。

GCD队列方式

主队列

dispatch_queue_t queue = dispatch_get_main_queue();
主队列是串行队列,任务是从上到下一个一个执行的。
dispatch_async(queue, ^{ NSLog(@"这是第一个任务,当前线程是:%@, 是否主线程 :%d ", [NSThread currentThread], [[NSThread currentThread] isMainThread]); });
上面这个函数意思是在主队列里面异步执行block里面的任务;

dispatch_sync(queue, ^{ NSLog(@"这是第一个任务,当前线程是:%@, 是否主线程 :%d ", [NSThread currentThread], [[NSThread currentThread] isMainThread]); });
上面这一行代码意思是在主线成中同步执行block块里面的任务,但是这样做会让主线程假死,无法执行任何操作,且不论你在任何队列里面同步执行一系列的任务,都会在主线程去执行。但是不会出现主线程假死,所以同步我们很少去用。(GCD里面注意的地方)

到了这里索性将同步执行任务和异步执行任务给大家解释一下吧,同步执行任务一定就是串行,异步执行任务并不一定说并行,跟我们的网络请求有些类似,同步及循序结构,代码一行一行的往下执行,异步及回调思想,某一时刻再执行block块里面的任务。

全局队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
全局队列是并行队列,任务时并行的,充分利用了现在多核cpu的优势,第一个参数为队列的优先级,第二参数为苹果预留参数现在没有实际作用我们一般填写0。
dispatch_async(queue, ^{ NSLog(@"这是第一个任务,当前线程是:%@, 是否主线程 :%d ", [NSThread currentThread], [[NSThread currentThread] isMainThread]); });
如果将一系列的任务加到全局队列里面时,任务就并发执行,从而没发预知那个任务先完成。

自定义队列

dispatch_queue_t queue = dispatch_queue_create("com.zouhao", DISPATCH_QUEUE_CONCURRENT);
自定义队列可以是并行的也可以是串行的,第一个参数是队列的名称也可以称之为标示吧,第二个参数是决定队列是串行还是并行的DISPATCH_QUEUE_CONCURRENT代表并行 DISPATCH_QUEUE_SERIAL代表串行

dispatch_async(queue, ^{ NSLog(@"这是第一个任务,当前线程是:%@, 是否主线程 :%d ", [NSThread currentThread], [[NSThread currentThread] isMainThread]); });
这个意思和上面两个意思基本一样,在自定队列里面加入了一个任务,任务就是block里面的业务。
那么队列就到这里吧,下面我们一起来看看GCD里面的一些比较常规的函数吧。

GCD延时执行,只执行一次,重复执行,分组任务,Barrier,函数指针

这是GCD里面的一些比较常规的使用方法,但是很多简友们在开发过程中,往往用一些奇怪的逻辑来避免使用这些方法,导致没有见过,或者使用过。下面我们一起来看看这些方式到底是干嘛用的吧。

GCD延时执行

//第一个参数是从现在开始,第二个参数是时间, dispatch_time_t delayInNanoSeconds =dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); //推迟两纳秒执行 dispatch_queue_t concurrentQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //第一个参数只延迟时间,第二个参数是在那个队列里,第三个是任务, dispatch_after(delayInNanoSeconds, concurrentQueue, ^(void){ NSLog(@"Grand Center Dispatch!"); });

GCD重复执行

//有时候项目需求我们重复执行一个方法多次,比如我们常见的项目了面的倒计时的计时器,很多都是用GCD实现的。 dispatch_queue_t queue = dispatch_queue_create("com.zouhao", DISPATCH_QUEUE_SERIAL); dispatch_apply(3, queue, ^(size_t index) { NSLog(@"%zu, %@", index, [NSThread currentThread]); });
这里我们看看dispatch_apply函数的几个参数分别是什么意思,第一个参数是重复执行多少次,第二个参数是在那个队列里面执行任务,第三个是任务,block里面的参数意思是第几次执行。

GCD只执行一次

//只执行一次,单例对象创建的时候经常需要用到它 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"只会执行一次"); });
这里看看diapatch_once函数后面跟着两个参数,第一个参数传了一个地址,第二个参数是任务,当执行到block块里面时,就会向第一个参数指针指向的地址写入信息,当下一次在执行这个代码时发现内存已经被写入过就不会再执行block了。

只执行一次在单例里面使用比较多,但是我们自己实现单例构造方法时,Class objcet = [[Class alloc] init];将他放在dispatch_once的block块内,然后觉得自己的单例完美了,其实不然,如果我们在外面调用init方法还是新对象,那么我们一般回去控制 initialize 在这个方法里面控制,详细的可以去百度。

分组任务

有时候我们开发的时候或许有需求需要讲队列里面的一些任务加到一个分组里面进行管理。当分组里面的任务执行完之后我们可能需要做一些其他的逻辑。

dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_async(group, queue, ^{ NSLog(@"第一个任务 %d, %@", [NSThread isMainThread], [NSThread currentThread]); }); dispatch_group_async(group, queue, ^{ NSLog(@"第二个任务 %d, %@", [NSThread isMainThread], [NSThread currentThread]); });
这样我们就讲所有的队列里面的任务用分组进行管理了,这个的队列可以使多个,当分组里面的任务执行完成之后我们用到了一个新的函数

//这个通知不能写在还所有任务之上,必须保证先在队列里面加入任务之后他才能够用,说简单的就是如果队列里面没有任务的时候他就会默认队列任务被执行完然后走通知 dispatch_group_notify(group, queue, ^{ NSLog(@"分组里面的最后一个任务了!"); });
但是这个任务不能第一个加到分组里,要不然这个时候分组为空,默认所有任务都执行完,直接对调通知的这个block。

GCD Barrier

当我们操作数据库的时候经常分为写与读两个过程,但是多线程操作往往存在一定的问题,比如我在读的时候正好在写入,这样无法保证数据的可行性,这个时候当我们写入的时候要阻断读写的线程,让其处于等候状态。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSLog(@"这是第一个读取数据的任务。。。线程是:%@, 是否主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]); }); dispatch_async(queue, ^{ NSLog(@"这是第二个读取数据的任务。。。线程是:%@, 是否主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]); }); dispatch_async(queue, ^{ NSLog(@"这是第三个任务读取数据的任务。。。线程是:%@, 是否主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]); }); dispatch_barrier_async(queue, ^{ NSLog(@"你哥哥给数据库里面写东西,不要打扰我"); }); dispatch_async(queue, ^{ NSLog(@"这是第四个读取数据的任务。。。线程是:%@, 是否主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]); }); dispatch_async(queue, ^{ NSLog(@"这是第五个读取数据的任务。。。线程是:%@, 是否主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]); }); dispatch_async(queue, ^{ NSLog(@"这是第六个任务读取数据的任务。。。线程是:%@, 是否主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]); });
很明显的可以看出来虽然是异步执行,但是在dispatch_barrierh函数上面的方法执行之后dispatch_barrierh下面的的任务讲处于等候状态,知道dispatch_barrierh函数里面的任务完成之后再去执行。

GCD函数指针

我们在调用GCD函数的时候发现很多方法很相似,只是函数名多了_f这就是我们的函数指针,下面我们一起看看有哪些

// dispatch_async_f(dispatch_get_main_queue(), "haha", func); // dispatch_sync_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>) // dispatch_after_f(<#dispatch_time_t when#>, <#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>) // dispatch_apply_f(<#size_t iterations#>, <#dispatch_queue_t queue#>, <#void *context#>, <#void (*work)(void *, size_t)#>) // dispatch_barrier_async_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>) // dispatch_barrier_sync_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>) // dispatch_group_async_f(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>) // dispatch_group_notify_f(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>) // dispatch_once_f(<#dispatch_once_t *predicate#>, <#void *context#>, <#dispatch_function_t function#>) // dispatch_set_finalizer_f(<#dispatch_object_t object#>, <#dispatch_function_t finalizer#>)
我们在实现对应的函数的时候一定要注意,函数的返回值为void,参数是void * 类型的,

为什么我们明明有了直接的函数调用还要出现函数指针实现任务体呢?很明显block是OC里面的语法块,但是函数确是C语言里面的语法,这样来看函数指针这种模式必然比较低层,那么效率必然会比block这种模式要高,但是GCD本生封装已经很完美,如果想单单通过讲block换成函数指针提高很多的效率是办不到的,只是在一些要求精细的项目里面会祈祷细微的提高效率的作用。

今天的多线程杂论就到这里了,如果大家有什么好的见解可以给我反馈,大家好共同的进步。下一篇为 XMPP实战,我将会讲这些demo全部上传到Github,在下一期的时候我会公布Github 的链接

你可能感兴趣的:(iOS多线程杂论)