Objective-C高级编程iOS和OS X多线程和内存管理(阅读笔记GCD篇)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yingBi2014/article/details/82804283

1.1 什么是GCD

Grand Central Dispatch是异步执行任务的技术之一。将应用程序中的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务,这样就比以前的线程更有效。

例子:

dispatch_async(queue, ^{
        /*
         长时间处理
         例如数据库访问,数据加载
         */
        
        
        dispatch_async(dispatch_get_main_queue(), ^{
            /*
             只在主线程中可执行的处理
             例如UI界面更新
             */
        });
 });

在导入GCD之前,Cocoa框架提供了NSObject类的performSelectorInBackground实例方法和perfromSelectorOnMainThread实例方法等简单的多线程技术,但是虽然比使用NSThread类要简单许多,但是与GCD相比还是GCD更高效和简洁。

1.2 多线程编程

一般我们写的代码是按照从上到下的顺序执行的,如下:

该源代码在Mac和iPhone上通过编译器转换为如下的CPU命令列(二进制代码):

汇集成CPU命令列和数据,将其作为一个应用程序安装到Mac或iPhone上。当程序启动时,该cpu命令列被配置到内存,由cpu从指定的地址开始一个一个的执行,由于一个cpu一次只能执行一个命令不能执行并列的分叉的命令,其执行不会出现分歧。这种一条无分叉的路径即为一条线程

多线程实际上是一种易发生各种问题的编程技术。比如多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程相互持续等待(死锁)、使用太多线程会消耗大量内存等。


应用程序在启动时,通过最先执行的线程,即主线程来描述用户界面。处理触摸屏幕的事件等。如果在主线程中执行长时间的处理,会妨碍主线程的执行,在OS X和iOS中会妨碍主线程中RunLoop的主循环的执行,从而导致不能更细用户界面。这就是长时间的处理不在主线程中执行而在其他线程中执行的原因。

2.1 Dispatch Queue

首先来回顾下苹果官方对GCD的说明

开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中

这句话源代码如下

dispatch_async(queue, ^{
        /*
         长时间处理
         例如数据库访问,数据加载
         */

    });

该源代码使用Block语法定义想执行的任务,通过dispatch_async函数追加赋值在变量queue的队列中,仅这样就可以使制定的Block在另一线程中执行。
Dispatch Queue是什么?是执行处理的等待队列。分为两种,串行和并行队列。即Serial Dispatch Queue 和 Concurrent Dispatch Queue。

串行队列使用一个线程按顺序执行,并行队列使用多个线程,由XUN内核决定使用的线程数。

2.2 Dispatch_queue_creat

通过dispatch_queue_creat可以创建线程,如下

dispatch_queue_t t = dispatch_queue_create("com.one.queue", DISPATCH_QUEUE_SERIAL);
或者
dispatch_queue_t t = dispatch_queue_create("com.one.queue", NULL);
或者
dispatch_queue_t t = dispatch_queue_create("com.one.queue", DISPATCH_QUEUE_CONCURRENT);

前两个是串行队列第三个是并行队列。

只在为了避免多线程问题之一–多线程更新相同资源导致数据竞争时使用串行队列。

在iOS6之后dispatch queue已经加入ARC管理范围,无需手动relese。

2.3 Main Dispatch Queue/Global Dispatch Queue(主队列和全局队列)

第二种方法是获取系统标准提供的队列。
Main dispatch queue正如其名称中含有的main一样,在主线程中执行的队列,因为主线程只有一个所以是一个串行队列。追加到Main dispatch queue的处理在主线程的RunLoop中执行,由于在主线程中执行,因此要将用户界面更新等一些必须在主线程执行的处理追加到Main dispatch queue使用。
Global Dispatch Queue是所有应用程序都能使用的一个并行队列。只需要直接获取就能使用。它有四个执行优先级,分别是高,默认,低和后台优先级。

在这里插入图片描述

2.4 dispatch_after(延时)

在3秒后将指定的block异步追加到主队列代码如下

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"3秒后执行");
    });

dispatch_after并不是3秒后执行,而是3秒后将block追加到主队列中,因为主队列在主线程的runloop中执行,所以在比如每隔1/60秒执行的runloop中,block最快在3秒后执行,最慢在3+1/60秒后执行,并且在主队列有大量处理任务有延迟时这个时间更长。

2.5 Dispatch_Group(派发分组)

在多线程异步执行任务多个任务结束后,想在所有任务执行完毕后执行其他操作,就需要用到dispatch_group

dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(group, globalQueue, ^{
        for(int  i=0 ; i<10;i++){
            NSLog(@"---1--%@",[NSThread currentThread]);
        }
    });
    dispatch_group_async(group, globalQueue, ^{
        for(int  i=0 ; i<10;i++){
            NSLog(@"---2--%@",[NSThread currentThread]);
        }
    });
    dispatch_group_async(group, globalQueue, ^{
        for(int  i=0 ; i<10;i++){
            NSLog(@"---3--%@",[NSThread currentThread]);
        }
    });
    
	//等到所有group完成后才会向下执行,永久等待
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    //等待3秒就向下运行,r==0代表group执行完成了,否则代表group还没执行完成
    long r = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)));
    NSLog(@"完成 %ld" , r);
    
    dispatch_group_notify(group, globalQueue, ^{
        NSLog(@"all block done");
    });

结果

另外,在dispatch_group中也可使用dispatch_group_wait函数仅等待全部处理结束。

//等到所有group完成后才会向下执行,永久等待
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

也可以设置等待时间,等待时间过了后再继续向下执行,

//等待3秒就向下运行,r==0代表group执行完成了,否则代表group还没执行完成
    long l = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)));
    NSLog(@"完成 %ld" , l);

2.6 dispatch_barier_async(栅栏)

对数据进行读取和写入时,如果只是读取操作的话是不会造成数据竞争的,只有在写入时会造成数据竞争,我们可以用dispatch_barier_asunc将读取操作和写入操作隔离开,

我们在blk3和blk4读取之间加入一次写入操作,如下:

2.7 dispatch_sync

dispatch_async意思是将制定的block异步加入到制定的队列中,不做任何等待。具有开辟新新线程的能力。
dispatch_sync则是同步添加,在追加的block结束之前,dispatch_sync函数会一直等待并阻塞当前线程。没有开辟新线程的能力。
在主线程中加入如下代码会造成死锁

dispatch_sync(dispatch_get_main_queue(), ^{
        
    });

这是因为sync会阻塞当前线程(主线程),等main_queue执行完block后,sync函数才会返回,但是主线程是被阻塞的无法去执行block,所以造成了死锁。

实际上,在任何一个串行队列中添加同步操作到当前队列都会造成该串行队列死锁。

2.8 dispatch_apply

dispatch_apply函数是dispatch_sync和dispatch queue 的关联api,该函数按指定的次数将指定的block追加到指定的dispatch queue中并等待全部处理结束。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%ld" , index);
    });
    NSLog(@"done");

因为dispatch_apply会等待所有的执行完再往下走,所以建议将dispatch_apply放在dispatch_async中执行。避免造成阻塞线程。

2.9 dispatch_once

dispatch_once是保证在应用程序中只执行一次指定处理的api。
在这里插入图片描述
通过diapatch_once函数,该初始化在多线程下也是百分之百安全。

3 其他说明

   //创建一个串行队列,最多只有一条线程,第二个参数写NULL默认也是串行队列
    dispatch_queue_t t = dispatch_queue_create("one", DISPATCH_QUEUE_SERIAL);
    //创建一个并行队列,可以含有多个线程
    dispatch_queue_t t2 = dispatch_queue_create("two", DISPATCH_QUEUE_CONCURRENT);
    //获取主线程队列(是一个默认串行队列)
    dispatch_queue_t t3 = dispatch_get_main_queue();
    //获取系统的全局队列(是一个默认并发队列),这里获得的优先级是默认的
    dispatch_queue_t t4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    /*
     串行队列:任务按顺序进行,最多只有一条线程,当在串行队列中同步添加block到当前串行队列中时造成线程锁死,主线程是个特殊的串行队列
     并发队列:任务并发进行,可以有多条线程,当和异步结合使用时能开辟多条线程,全局队列(GlobalQueue)是个特殊的并发队列
     async:异步添加block操作,不阻塞当前线程,不用等block执行完就会继续向下执行,跟并行队列结合有开辟线程的功能
     sync:同步添加block操作,阻塞当前线程,当block执行完后函数才返回,不具有开辟新线程的功能
     */

图书pdf下载链接: https://pan.baidu.com/s/1g4oyRULN8B-Ew0z7A4Z5xA 提取码: 9gdz
参考博客
https://mp.weixin.qq.com/s/CPpy2zqugyPwNzGLOonUGw
https://blog.csdn.net/ycf03211230/article/details/79565966

猜你喜欢

转载自blog.csdn.net/yingBi2014/article/details/82804283
今日推荐