OC多线程之GCD

要了解多线程首先要知道什么是进程,什么是进程?算法

正在进行中的程序被称为进程,负责程序运行的内存分配
每个进程都有本身独立的虚拟内存空间
 
什么是线程:
线程是进程中一个独立的执行路径(控制单元)
一个进程中至少包含一条线程,即主线程
能够将耗时的执行路径(如:网络请求)放在其余线程中执行
建立线程的目的就是为了开启一条新的执行路径,运行指定的代码,与主线程中的代码实现同时运行
 
线程的优缺点:
优点
(1)充分发挥多核处理器优点,将不一样线程任务分配给不一样的处理器,真正进入“并行运算”状态
(2)将耗时的任务分配到其余线程执行,由主线程负责统一更新界面会使应用程序更加流畅,用户体验更好
(3)当硬件处理器的数量增长,程序会运行更快,而程序无需作任何调整
弊端
新建线程会消耗内存空间和CPU时间,线程太多会下降系统的运行性能
误区
多线程技术是为了并发执行多项任务,不会提升单个算法自己的执行效率
 
iOS开发中有哪些多线程技术?
NSThread
(1)使用NSThread对象创建一个线程很是方便
(2)可是!要使用NSThread管理多个线程很是困难,不推荐使用
(3)技巧!使用[NSThread currentThread]跟踪任务所在线程,适用于这三种技术
NSOperation/NSOperationQueue
(1)是使用GCD实现的一套Objective-C的API
(2)是面向对象的线程技术
(3)提供了一些在GCD中不容易实现的特性,如:限制最大并发数量、操做之间的依赖关系
GCD —— Grand Central Dispatch
(1)是基于C语言的底层API
(2)用Block定义任务,使用起来很是灵活便捷
(3)提供了更多的控制能力以及操做队列中所不能使用的底层函数
 
提示:iOS的开发者,须要了解三种多线程技术的基本使用,由于在实际开发中会根据实际状况选择不一样的多线程技术
 
 
GCD的基本思想
(1)操做使用Blocks定义
(2)队列负责调度任务执行所在的线程以及具体的执行时间
(3)队列的特色是先进先出(FIFO)的,新添加至对列的操做都会排在队尾
 
GCD中队列分为两类,一种是串行队列,一种是并行队列.可是无论什么队列都是先添加先被分出去-----即先进先出(FIFO)
---串行队列会将添加到队列里面的任务按添加顺序一个一个去执行而且上一个执行完才会执行下一个
---并行队列则不会按照添加顺序去执行任务,若是向并行队列里面添加了任务1,2,3,4,5,6,7,8,9,10
那么系统会开几条线程一并去执行这些任务,若是系统建立了4条线程去执行这10个任务,系统会先将任务1,2,3,4分配给四条线程(a,b,c,d),而后若是其中一个线程处理完一个任务,这个空闲的线程就会去执行第5个任务,可是刚才哪一个线程把任务作完了是没法肯定的有多是线程b有多是线程c等等都有可能
就上面的例子而言,串行队列会只会建立一个线程去执行任务,先执行任务1,任务1完成后再执行任务2,任务2执行完成后执行任务3,以此类推.
 
 
下面咱们来用代码看一下:
#pragma mark - 串行(一个接一个,排队跑步,保持队形)队列
- (void)gcdDemo1
{
    // 将操做放在队列中// 使用串行队列,的异步任务很是很是很是有用!新建子线程是有开销的,不能无休止新建线程
    // 建立一个串行队列
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);
    
    // 非ARC开发时,千万别忘记release
//    dispatch_release(q);for (int i = 0; i < 10; ++i) {
        // 异步任务,并发执行,可是若是在穿行队列中,仍然会依次顺序执行
        dispatch_async(q, ^{
            // [NSThread currentThread] 能够在开发中,跟踪当前线程
            // num = 1,表示主线程
            // num = 2,表示第2个子线程。。。
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
}

上面的代码是建立一个串行队列,并添加10个任务到队列中,运行这个方法时,你会看到控制台输出的顺序是从1到10的,证实这10个任务是一个执行完在执行另外一个,而且是按照添加顺序来的网络

 

再来看一下并发队列里面添加10个任务会怎样多线程

 dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcd2", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; ++i) {
        // 同步任务顺序执行
        dispatch_async(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }

从控制台的打印信息来看,你会看到打印是无序的,因此能够验证,并行队列不是(((按照顺序  && 执行完一个才执行一个的)))并发

可是前四个打印中确定有(0,1,2,3){若是开启了四个线程的话}中的一个,由于并行队列里面前四个是0,1,2,3,因此前四个打印中,确定会有其中一个异步

 

同时咱们注意到上面向队列里面添加任务时候都是用的dispatch_async异步执行,那若是用dispatch_sync同步执行会是什么结果呢?async

那什么是同步操做,什么时异步操做呢?
同步操做不会建立新的线程当前代码执行时是哪一个线程,这些任务就会被添加到那个线程里去执行
异步操做会建立一个新的线程,而后去执行任务(并不是绝对,若是任务被添加主队列"主队列是个特殊的队列,主线程所在的队列")
 
下面咱们来看一下并行队列里执行同步任务会是什么结果
 dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcd2", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; ++i) {
        // 同步任务顺序执行
        dispatch_sync(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }

根据控制台的输出咱们能够看到,输出是按顺序来的,而且打印出来的Tread信息都是主线程,证实上面任务的执行都是在主线程上完成的函数

(由于因此的任务都按顺序从队列里面出来,而且处理的线程就一个,因此会挨个执行)性能

由于这个代码所在的线程就是主线程,因此sync就在本线程中执行(不开辟新线程)spa

 

一样的,咱们来看一下串行队列里的同步任务执行结果:线程

  dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);
   
    for (int i = 0; i < 10; ++i) {
        // 同步任务顺序执行
        dispatch_sync(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }

你会看到跟上面同样的结果

 

下面咱们再来看一下并行队列时的一些问题

- (void)gcdDemo2
{
   
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcd2", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; ++i) {
        // 异步任务(第一批任务)
        dispatch_async(q, ^{
            // [NSThread currentThread] 能够在开发中,跟踪当前线程
            // num = 1,表示主线程
            // num = 2,表示第2个子线程。。。
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }

    
    for (int i = 0; i < 10; ++i) {
        // 同步任务顺序执行(第二批任务)
        dispatch_sync(q, ^{
            NSLog(@"%@ %dkkkkkk", [NSThread currentThread], i);
        });
    }
    
    
    
    
}

你会看到第一批任务里面的打印会无序的打印出来,第二批任务会按照顺序打印(带有kkkk的),可是第一批跟第二批任务是穿插进行的有时候打印第一批里面的,有时候打印第二批里面的

而且已还回发现打印第一批任务的有好几个线程(线程2,线程3...),,打印第二批任务的只有主线程

因此从这里能够看出,CPU执行任务的时候是给各个线程轮流使用的

 

可是你再看看下面的代码

#pragma mark - 并行(并排跑,相似于赛跑)
- (void)gcdDemo2
{
    
    // 并行队列容易出错!并行队列不能控制新建线程的数量!
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcd2", DISPATCH_QUEUE_CONCURRENT);
    //第一轮任务
    for (int i = 0; i < 10; ++i) {
        // 同步任务顺序执行
        dispatch_sync(q, ^{
            NSLog(@"%@ %dkkkk", [NSThread currentThread], i);
        });
    }
    
    // 第二轮任务
    for (int i = 0; i < 10; ++i) {
        // 异步任务
        dispatch_async(q, ^{
            // [NSThread currentThread] 能够在开发中,跟踪当前线程
            // num = 1,表示主线程
            // num = 2,表示第2个子线程。。。
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
    
    
}

你会发现第一轮任务里面的按照1,2,3,4,5,6,7.....这样打印出来,而且第一轮任务执行完毕才会执行第二轮任务

并且第二轮任务会无顺序的打印出数字,这又为何呢,就跟上一份代码两批任务的添加顺序呢换了一下,结果差异怎么那么大呢?

由于第一轮任务使用的同步方法,什么是同步方法呢?同步就是不会建立新的线程,当执行到那个同步任务的时候,当前的线程是谁,谁就去执行这些任务

而上面的代码中,执行到同步任务的是主线程(num = 1),因此主线程就会按照顺序去执行打印任务

 

而第二批任务是异步方法,异步方法会建立新的线程去执行任务,因此们的ThreadNum=2,3,4等等等,而且执行顺序是没法预估的(无序打印)

到这里你可能还会有一个疑问就是为何两批任务不会像上一份代码那样,穿插着执行,而是先执行第一批在执行第二批呢?

这事由于第一批里面用了同步而且执行到第一批的是主线程,下面咱们来详细解释一下

主线程是伴随着程序运行而运行的,只要程序不挂掉,主线程就会一直运行,线程处理任务的时候是执行完一个,在执行第二个,一个线程不可能同时执行多个任务

,并且其余线程都是由主线程去建立的,主线程就相似于一个超牛逼的人,而且他会造人技术,他须要别人帮忙的时候就会造一我的出来去帮他干活,

可是这个超牛逼人每次只能干一件事,干完一件事才能干另外一件,,,,你仔细看上面的代码就会发现,第一批的10个任务是先添加到主线程里面的,第二批的任务是后来出现的而且须要新的人去执行,,,,,,单此时主线程(超牛逼的那我的)已经有10个任务了,他必须先把这10个任务干完才能去开辟一个新的线程(造人帮他干活),因此

他会先把这10个任务完成之后,他才会开辟新的线程去帮他执行另外的任务,,,,,,,,,从这里就能够很轻松的理解为何先添加同步任务和先添加异步任务结果不一样了

(先添加异步任务...上上份代码... 就是先把造人的事情分给了主线程,然后才给他分配的打印的任务),因此他造出来的人会和本身同时执行任务

 

 

 

由于每次建立队列都很麻烦,因此苹果给咱们提供了两个能够快速获取的队列---主队列和全局队列

下面是获取代码

    // 每个应用程序都只有一个主线程
    // 为何须要在主线程上工做呢?
    // 在iOS开发中,全部UI的更新工做,都必须在主线程上执行!
    dispatch_queue_t q = dispatch_get_main_queue();


    // 全局队列与并行队列的区别
     // 1> 不须要建立,直接GET就能用
    // 2> 两个队列的执行效果相同
    // 3> 全局队列没有名称,调试时,没法确认准确队列
    
    // 记住:在开发中永远用DISPATCH_QUEUE_PRIORITY_DEFAULT
    // 多线程的优先级反转!低优先级的线程阻塞了高优先级的线程!
    dispatch_queue_t q =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

上面两行代码分别是获取主队列和全局队列的方法

添加到主队列里的任务会放在主线程里执行,这里须要注意的是,若是你使用了主队列,必定不要使用sync同步方法,而要是用异步方法

这个事为何呢?1.主队列是一个串行队列,会依次执行任务,执行完一个执行另外一个

        2.sync同步方法不会建立新的线程,会在执行到那句代码所在的线程执行

       由于主线程会一直执行到程序关闭,因此主线程里面的任务是执行不完的,若是在主队列里添加同步任务,由于主线程里的任务是执行不玩的,因此你添加的那个任务永远不会被执行(这里请注意,主队列和主线程不是一回事,线程是处理任务的, 队列是给线程送任务的)

 

在使用主队列时应该使用sync异步方法去执行任务,这样不用等主线程执行完毕就能够执行到任务,须要注意的是在主队列里不会建立新的线程,即便使用async一步方法也不会建立新的线程

可能上面的主队列比较难理解,下面来看个好理解的

#pragma mark 同步任务的阻塞
- (void)gcdSyncDemo
{
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);
    
    // 任务1
    dispatch_sync(q, ^{
        NSLog(@"sync %@", [NSThread currentThread]);
        
        // 任务2,同步里面包含同步,会形成阻塞
        dispatch_sync(q, ^{
        // 永远也不会执行到
            NSLog(@"async %@", [NSThread currentThread]);
        });
    });
}

上面是一个任务的嵌套,由于添加顺序是任务1先添加,任务2后添加,而且是同步队列,多以任务2会等任务1完成之后再执行,可是此时任务2在任务1的里面,若是任务2没有执行完,任务1是不可能执行完的,此时就是任务1等任务2完成 任务2,等任务1完成,两个任务都等待对方完成本身才能完成,这样两个任务都不会完成

 

而主队列里面添加同步任务就相似上面的状况,主队列里面一个巨大的任务等待小的同步任务完成,二小的同步任务等待包含他的巨打的任务完成,形成相互等待,结果就是谁完成不了

相关文章
相关标签/搜索