多线程随笔

  1 ===================
  2 
  3 多线程
  4 
  5 ==================
  6 
  7  
  8 
  9 多线程
 10 
 11 一、概念:
 12 
 13 程序:(Program)(App)是一个可以运行的文件(我们写的代码)
 14 
 15 进程:(Process)是程序执行的一个操作实体
 16 
 17         即在系统中正在运行的一个应用程序
 18 
 19     每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内
 20 
 21     比如同时打开QQ、Xcode,系统就会分别启动2个进程
 22 
 23     通过“活动监视器”可以查看Mac系统中所开启的进程
 24 
 25 01_iOS进程.png
 26 
 27 02_进程和系统.png
 28 
 29 线程:(Thread)线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行    
 30 
 31 一个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)
 32 
 33     比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行
 34 
 35 03_进程和线程.png
 36 
 37 二、背景:
 38 
 39     线程的串行
 40 
 41     1个线程中任务的执行是串行的
 42 
 43     如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
 44 
 45     也就是说,在同一时间内,1个线程只能执行1个任务
 46 
 47     比如在1个线程中下载3个文件(分别是文件A、文件B、文件C)
 48 
 49  
 50 
 51 当用户播放音频、下载资源、进行图像处理时往往希望做这些事情的时候其他操作不会被中断或者希望这些操作过程中更加顺畅。在单线程中一个线程只能做一件事情,一件事情处理不完另一件事就不能开始,这样势必影响用户体验。
 52 
 53 耗时操作
 54 
 55   不依赖逻辑部分的东西
 56 
 57  
 58 
 59 意义:
 60 
 61 随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双核、甚至四核时代。如何充分发挥这些CPU的性能,会变得越来越重要。在iOS中如果想要充分利用多核心CPU的优势,就要采用并发编程,提高CPU的利用率。
 62 
 63  
 64 
 65 所以这节课学好了,会是面试的亮点
 66 
 67  
 68 
 69 三、多线程
 70 
 71  
 72 
 73 1.多线程原理
 74 
 75 串行并行
 76 
 77 04_线程的串并行.png
 78 
 79  
 80 
 81 同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
 82 多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
 83 如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
 84 思考:如果线程非常非常多,会发生什么情况?
 85 CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
 86 每条线程被调度执行的频次会降低(线程的执行效率降低)
 87 
 88  
 89 
 90 2.多线程的优缺点
 91 
 92  
 93 
 94 多线程的优点
 95 
 96 能适当提高程序的执行效率
 97 
 98 能适当提高资源利用率(CPU、内存利用率)
 99 
100  
101 
102 多线程的缺点
103 
104 开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
105 
106 线程越多,CPU在调度线程上的开销就越大
107 
108 程序设计更加复杂:比如线程之间的通信、多线程的数据共享
109 
110  
111 
112 3.多线程在iOS开发中的应用
113 
114  
115 
116 主线程:一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”
117 
118 主线程的主要作用
119 
120 显示刷新UI界面
121 
122 处理UI事件(比如点击事件、滚动事件、拖拽事件等)
123 
124 主线程的使用注意:别将比较耗时的操作放到主线程中。
125 
126 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验
127 
128  
129 
130 4.iOS的线程模型
131 
132 pthread(底层C线程库)
133 
134 NSThread(OC线程库)
135 
136 NSOperationQueue(线程池/线程队列)
137 
138 GCD(Blocks模式的线程池)
139 
140 05_iOS线程模型.png
141 
142  
143 
144 四、整体认知结束,详见代码
145 
146  
147 
148  
149 
150 01_TimeConsumingAndPhread
151 
152  
153 
154 这个工程告诉我们:
155 
156     //1.谁来响应我们的UI操作? 都是主线程响应
157 
158     //2.主线程处理任务,都是按照自然顺序处理,谁先来先处理谁,在一件事没有处理完成之前,这个家伙不会抽身去处理其他的事儿
159 
160     //3.currentThread 获得当前逻辑代码是运行在那一个线程中
161 
162     //4.主线程的number = 1 永远是 1
163 
164  
165 
166     //请问我们应该在主线程上执行什么样的操作呢?
167 
168     //主线程上执行的操作最好都是及时响应的,不能让用户觉得我们的程序像windows中的程序一样卡死了,这是一个很严重的用户体验问题
169 
170  
171 
172     //如果我真的需要完成一个非常耗时间的操作怎么办?
173 
174     //1.一定不要在主线程做这件事
175 
176     //2.开启一条新的线程(子线程),来完成耗时操作
177 
178     //3.常见耗时操作
179 
180     //大量的开模运算(数学类运算),图形运算,OpenGLES2.0(一般接触不到)
181 
182     //网络操作(1.网络环境影响(2G,2.5G,3G,4G相应速度都不同,大数据下载操作))
183 
184    
185 
186             pthread:C方法创建子线程
187 
188     pthread_t cThread;
189 
190     int b = pthread_create(&cThread, NULL, working, NULL);
191 
192 缺点:不好写,不好控
193 
194 优点:只是开个线程而已
195 
196  
197 
198  
199 
200  
201 
202 02_NSThreadAndNSLock
203 
204  
205 
206 这个工程告诉我们:
207 
208 一、
209 
210     //NSThread 是 cocoa(MacOS,iOS)中一个轻量级的多线程对象
211 
212     //NSThread 傻瓜式的操作,简单
213 
214     //1.如何创建一个子线程
215 
216     //2.把耗时操作的逻辑转移到子线程中去
217 
218     //object 给 @selector中要执行的方法传递参数
219 
220     
221 
222 //    //方法一
223 
224 //    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(doWCing) object:nil];
225 
226 //    //可以给子线程命名
227 
228 //    thread.name = @"子线程1";
229 
230 //    //使用alloc创建的线程,一定要显示的调用start方法,才能够运行
231 
232 //    [thread start];
233 
234     
235 
236     //方法二
237 
238     // 创建一个线程  只是创建就好 不亲自执行这个线程 只是创建
239 
240     // 线程创建后 就会自动运行
241 
242     //为了更加简化我们创建一个子线程的操作,NSObject对创建线程封装了一些方法
243 
244     //内部会自动的创建一个子线程,并且把@selector中的方法交给子线程去做
245 
246     [NSThread detachNewThreadSelector:@selector(doWCing) toTarget:self withObject:nil];
247 
248     
249 
250     //线程工作完程,如果以后不再使用这个线程
251 
252     //应当做如下操作
253 
254     //    [thread cancel];
255 
256     [NSThread exit];
257 
258  
259 
260 二、
261 
262     //线程锁 类比上厕所,多个线程有一把锁就够了。谁锁,谁才能打开。
263 
264     NSLock * _threadLock;
265 
266     /**
267 
268      *  (atomic)。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
269 
270      *
271 
272      //通过加锁和解锁,使一段代码成为原子操作。
273 
274      //使用线程锁,不是服务于线程,而是服务于代码。当我们希望一段代码进行原子操作,如数据库的写入,就可以进行加锁。
275 
276      //比如从判断缓存是否存在,到写入新的缓存。应当加一把线程锁。即使我们没有创建NSThread。
277 
278      //它和@Syncronized关键字起到同样效果。但是关键字不能在一个函数中加锁,另一个函数中解锁,NSLock可以。
279 
280      */
281 
282     //上锁
283 
284     [_threadLock lock];
285 
286     //解锁
287 
288     [_threadLock unlock];
289 
290  
291 
292 三、
293 
294     //NSThread 很难人为的去控制创建子线程的数量
295 
296     //子线程创建过多,会严重的影响程序的性能,因为每一个线程都会占用一定cpu的资源,导致cpu很忙碌,特别是多人开发的时候
297 
298  
299 
300  
301 
302 03_GCDDemo
303 
304  
305 
306 这个工程告诉我们:
307 
308  
309 
310 1.什么是GCD?
311 
312 为并发代码在多核硬件(ios(iphone4s),mac os)上高效执行多线程代码而生的技术
313 
314 弥补了NSThread难于管理的问题
315 
316 //Grand Central Dispatch(多线程优化技术)或称GCD,是一套底层API,提供了新的模式编写并发执行的程序。允许将一个程序切分为多个单一任务,然后提交到工作队列并并发地或者串行地执行。
317 
318  
319 
320 2.什么是Queue队列?
321 
322 GCD使用了队列概念,已经很好的解决了NSThread难于管理的问题,队列实际上就是一个数组的概念,通常我们把要执行的任务,放到一个队列中去管理
323 
324 //GCD采用队列的方式管理线程,不仅可以管理多线程,并发的任务,设置主线程,也通过任务队列的方式进行管理。
325 
326 //所以说GCD的队列是任务队列,而不是线程队列。
327 
328  
329 
330 3.什么是串行队列?
331 
332 依次完成每一个任务
333 
334  
335 
336 4.什么是并行队列?
337 
338 好像所有的任务都是在同一时间执行的
339 
340  
341 
342 5.mainqueue(主队列)串行队列,全局队列(Global Queue),自己创建队列
343 
344  
345 
346  
347 
348 CGD存在的问题
349 
350 1.纯c的代码,让很多面向对象程序员头疼
351 
352 2.虽然已经很好的解决了NSThread难于管理的问题,但是还不是很好,当程序在某一个时间点上如果需要执行很多个好时操作,也会出现创建很多个线程的问题,导致系统性能出现问题
353 
354  
355 
356 优点:
357 
358 通过GCD,开发者不用再直接跟线程打交道了,只需要向队列中添加block代码即可,GCD在后端管理着一个线程池。GCD不仅决定着哪个线程(block)将被执行,它还根据可用的系统资源对线程池中的线程进行管理——这样可以不通过开发者来集中管理线程,缓解大量线程的创建,做到了让开发者远离线程的管理。
359 
360 缺点:
361 
362 虽然GCD是稍微偏底层的一个API,但是使用起来非常的简单。不过这也容易使开发者忘记并发编程中的许多注意事项和陷阱。
363 
364  
365 
366  
367 
368  
369 
370 一、Dispatch Queues
371 
372  
373 
374     1.用户队列(用户队列,串行执行):
375 
376     用户队列(GCD中对这种队列没有特定的名词来描述,姑且如此称之)可以使用dispatch_queue_create函数创建队列。这些队列是串行执行的。
377 
378     //怎么使用呢?
379 
380     //想要执行GCD的方法dispatch_async 必须把它交个一个队列管理,否则报错
381 
382     //dispatch_async 开一个子线程,不会阻塞主线程的操作,操作对象是线程,而不是方法
383 
384     //向队列中添加block任务
385 
386     //这是可以选择任务是同步执行还是异步执行
387 
388     //dispatch_async 函数会将传入的block块放入指定的queue里运行。这个函数是异步的,这就意味着它会立即返回而不管block是否运行结束。
389 
390     //同步,失去了多线程的意义,只不过是拿出来放在自己的队列里而已
391 
392     
393 
394     //把任务都添加在了同一队列(线程)queue上
395 
396     //所以自己创建的队列是串行的
397 
398  
399 
400  
401 
402  
403 
404     2.Global queues(全局队列,并行执行):
405 
406     全局队列是并发队列,并由整个进程共享。进程中存在三个全局队列,可以调用dispatch_get_global_queue函数,传入优先级来访问队列。
407 
408  
409 
410     抓取全局的队列
411 
412     global_queue并行队列
413 
414     可以同时运行多个任务,每个任务的启动时间是按照加入queue的顺序,即任务开始执行的顺序和其加入队列的顺序相同,结束的顺序依赖各自的任务.我们自己不能去创建并行调度队列。只有三个可用的global concurrent queues。使用dispatch_get_global_queue获得
415 
416     //由于GCD中同一个队列中的任务是串行执行的。所以只要将那些需要线程保护的任务添加到同一个队列当中,就能实现串行执行。
417 
418  
419 
420  
421 
422 3.The main queue(主队列,串行执行):
423 
424     提交到main queue的任务将在主线程执行。main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列。
425 
426     //至少两个线程会调用这个函数,如果这个函数中有需要原子操作的代码,应当加线程锁,或者使用GCD进行保护
427 
428     
429 
430     //使用GCD保护
431 
432     //将需要保护的代码交由主线程执行。是主线程,也是主队列,一个队列的任务,是串行的。
433 
434 PS:
435 
436 还有一些其他的东西不需要了解的,工作基本不用,面试就说知道这个东西,但是不常用,如果需要可以细查查
437 
438 讲这些只会加大我们的负担,影响学习心情和进度,意义不大
439 
440  
441 
442 比如GCD的内存管理
443 
444 比如GCD的线程阻碍等。。。
445 
446  
447 
448  
449 
450  
451 
452 04_GCDMakeSingleton
453 
454  
455 
456 GCD具有以下优点:
457 
458  
459 
460 GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。
461 
462 GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
463 
464 GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。
465 
466  
467 
468  
469 
470 单例模式是开发中最常用的写法之一,创建一个单例很多办法,iOS的单例模式有两种官方写法,如下:
471 
472  
473 
474 不使用GCD
475 
476 #import "ServiceManager.h"
477 
478  
479 
480 static ServiceManager *defaultManager;
481 
482  
483 
484 @implementation ServiceManager
485 
486  
487 
488 +(ServiceManager *)defaultManager{
489 
490     if(!defaultManager)
491 
492     defaultManager=[[self allocWithZone:NULL] init];
493 
494     return  defaultManager;
495 
496 }
497 
498  
499 
500 @end
501 
502  
503 
504 某些特殊情况下,if执行后面执行的慢了,然后另一个线程用了他,就会创建,if并不安全
505 
506  
507 
508 使用GCD
509 
510 #import "ServiceManager.h"
511 
512  
513 
514 @implementation ServiceManager
515 
516  
517 
518 +(ServiceManager *)sharedManager{
519 
520     static dispatch_once_t predicate;
521 
522     static ServiceManager * sharedManager;
523 
524     dispatch_once(&predicate, ^{
525 
526     sharedManager=[[ServiceManager alloc] init];
527 
528     });
529 
530     return sharedManager;
531 
532 }
533 
534  
535 
536 @end
537 
538  
539 
540 dispatch_once这个函数,它可以保证整个应用程序生命周期中某段代码只被执行一次!
541 
542  
543 
544 1. 线程安全。
545 
546 2. 满足静态分析器的要求。
547 
548 3. 兼容了ARC
549 
550  
551 
552 我们看到,该方法的作用就是执行且在整个程序的声明周期中,仅执行一次某一个block对象。简直就是为单例而生的嘛。而且,有些我们需要在程序开头初始化的动作,如果为了保证其,仅执行一次,也可以放到这个dispatch_once来执行。
553 
554 总结:1.这个方法可以在创建单例或者某些初始化动作时使用,以保证其唯一性。2.该方法是线程安全的,所以请放心大胆的在子线程中使用。(前提是你的dispatch_once_t *predicate对象必须是全局或者静态对象。这一点很重要,如果不能保证这一点,也就不能保证该方法只会被执行一次。)
555 
556  
557 
558  
559 
560 //单例创建的写法
561 
562 http://blog.csdn.net/lovefqing/article/details/8516536
563 
564 //单例创建写法的原因
565 
566 http://blog.sina.com.cn/s/blog_4cd8dd130101mi37.html
567 
568  
569 
570  ios 中如何实现一个真正的单利模式
571 
572  一个常见的担忧是它们常常不是线程安全的。这个担忧十分合理,基于它们的用途:单例常常被多个控制器同时访问。
573 
574  
575 
576  1.首先要保证allocWithZone是线程安全,当中调用super方法的时候使用dispatch_once方法锁住
577 
578  2.还要保证单利实现方法中也使用了dispatch_once方法锁住创建对象过程
579 
580  
581 
582         //dispatch_once的作用正如其名:对于某个任务执行一次,且只执行一次。 dispatch_once函数有两个参数,第一个参数predicate用来保证执行一次,第二个参数是要执行一次的任务block。
583 
584         //dispatch_once被广泛使用在单例、缓存等代码中,用以保证在初始化时执行一次某任务。
585 
586         //dispatch_once在单线程程序中毫无意义,但在多线程程序中,其低负载、高可依赖性、接口简单等特性,赢得了广大消费者的一致五星好评。
587 
588  
589 
590  
591 
592     //(mutableCopy深 与Copy浅,字面理解,能变的是深拷贝)
593 
594     //浅拷贝,就是只创建一个同类型对象返回
595 
596     //深拷贝就是,不但创建一个同类型对象回去,并且这个对象中所有的属性值一并都赋值过来
597 
598     //深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
599 
600     //copy是创建一个新对象(copy 是内容拷贝),retain是创建一个指针,引用对象计数加1(指针拷贝)
601 
602     //使用区别
603 
604     //mutableCopy得到一个新的可变对象,可以看到它的地址和原来对象的地址是不同的,也就是新对象的retainCount从0-1。
605 
606     //而copy得到的是一个不可变对象,这里分为两种情况:1、如果原来的对象也是一个不可变的,那么他们的地址指向同一个地址,也就是说它们同一个对象,只是把retainCount加1了而已,2、原来的对象是一个可变对象,那么它会新生成一个不可变对象,地址不同,也是retainCount从0-1。
607 
608  
609 
610  
611 
612 05_NSOperationQueue
613 
614 NSOperation使用方法
615 
616     iOS中有多种多线程实现方式,苹果公司建议我们都使用NSOperation技术
617 
618     1.GCD是纯c的对于面向对象程序员来说非常不友好
619 
620     2.GCD对线程的管理还不是很强大
621 
622  
623 
624     NSOperation底层实现就是基于GCD来做的
625 
626  
627 
628     dispatch_queue  == NSOperationQueue
629 
630     dispatch_asyn() == NSOperation
631 
632     dispatch_syn()  == NSOperation
633 
634  
635 
636  
637 
638     iOS并发编程中,把每个并发任务定义为一个Operation,对应的类名是NSOperation。NSOperation是一个抽象类,无法直接使用,它只定义了Operation的一些基本方法。我们需要创建一个继承于它的子类或者使用系统预定义的子类。目前系统预定义了两个子类:NSInvocationOperation和NSBlockOperation。
639 
640  
641 
642  
643 
644     NSInvocationOperation
645 
646         NSInvoationOperation是一个基于对象和selector的Operation,使用这个你只需要指定对象以及任务的selector,如果必要,你还可以设定传递的对象参数。
647 
648         NSInvocationOperation *invacationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomethingWithObj:) object:obj];
649 
650  
651 
652         同时当这个Operation完成后,你还可以获取Operation中Invation执行后返回的结果对象。
653 
654         id result = [invacationOperation result];
655 
656  
657 
658     NSBlockOperation
659 
660         在一个Block中执行一个任务,这时我们就需要用到NSBlockOperation。可以通过blockOperationWithBlock:方法来方便地创建一个NSBlockOperation:
661 
662         NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
663 
664         //Do something here.
665 
666         }];
667 
668  
669 
670     运行一个Operation
671 
672         调用Operation的start方法就可以直接运行一个Operation。
673 
674         [operation start];
675 
676     取消一个Operation
677 
678         要取消一个Operation,要向Operation对象发送cancel消息:
679 
680         [operation cancel];
681 
682  
683 
684  
685 
686 06_ThreadsCommunication
687 
688 多种主线程的获取方式(即线程之间的通信)
689 
690  
691 
692 //通过NSThread
693 
694     //假设是子线程执行该方法,如何回调主线程,修改UI
695 
696     [self performSelectorOnMainThread:@selector(finished) withObject:nil waitUntilDone:NO];
697 
698     //这个方法是NSObject的类别方法,所有对象都能调用。
699 
700     //当前线程回调主线程完成工作,第三个参数是如果传YES,则当前线程等待主线程完成这一工作后,继续执行,否则阻塞。如果传NO,则当前线程不阻塞。
701 
702  
703 
704  
705 
706 //通过operationQueue
707 
708     //线程队列
709 
710     //通过这个方法找到主队列,将任务添加给主队列去完成,即可交付给主线程完成。
711 
712     NSOperationQueue * mainQueue = [NSOperationQueue mainQueue];
713 
714  
715 
716     [mainQueue addOperationWithBlock:^(void){
717 
718     NSLog(@"判断执行当前block的是否是主线程 %d",[NSThread isMainThread]);
719 
720     }];
721 
722  
723 
724 //通过GCD
725 
726     dispatch_async(dispatch_get_main_queue(), ^(void){
727 
728     NSLog(@"主线程执行这里的语句");
729 
730     });
731 
732  
View Code
原文地址:https://www.cnblogs.com/sdutmyj/p/4767853.html