OC多线程之GCD

时间:2022-04-19 05:18:35

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

正在进行中的程序被称为进程,负责程序运行的内存分配
每一个进程都有自己独立的虚拟内存空间
 
什么是线程:
线程是进程中一个独立的执行路径(控制单元)
一个进程中至少包含一条线程,即主线程
可以将耗时的执行路径(如:网络请求)放在其他线程中执行
创建线程的目的就是为了开启一条新的执行路径,运行指定的代码,与主线程中的代码实现同时运行
 
线程的优缺点:
优势
(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同步执行会是什么结果呢?

那什么是同步操作,什么时异步操作呢?
同步操作不会创建新的线程当前代码执行时是哪个线程,这些任务就会被添加到那个线程里去执行
异步操作会创建一个新的线程,然后去执行任务(并非绝对,如果任务被添加主队列"主队列是个特殊的队列,主线程所在的队列")
 
下面我们来看一下并行队列里执行同步任务会是什么结果
 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就在本线程中执行(不开辟新线程)

 

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

  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完成,两个任务都等待对方完成自己才能完成,这样两个任务都不会完成

 

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