zoukankan      html  css  js  c++  java
  • 多线程随笔

      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
  • 相关阅读:
    JS(原生语法)_实现酷酷的动态简历
    Linux外在设备的使用
    查看系统内存信息
    查看CPU信息
    查看系统PCI设备
    配置网络
    Linux分区
    Observer
    Singleton
    Open closed principle
  • 原文地址:https://www.cnblogs.com/sdutmyj/p/4767853.html
Copyright © 2011-2022 走看看