iOS开发 多线程之”GCD“

GCD

GCD全称是GrandCentralDispatch,可译为“⽜逼的中枢调度器”

  • 纯C语⾔言,提供了⾮常多强⼤的函数
  • GCD的优势
  • GCD是苹果公司为多核的并⾏行运算提出的解决⽅方案
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务(就是把任务放到队列里)不需要编写任何线程管理代码

两个核心概念

任务: 需要执行的对象
队列:储存任务的框框

程序员需要做的两件事

  • 定制任务
    添加任务到队列(队列的取出规则:先进先出,后进后出)

  • 执⾏任务 :

    • 同步执行:一人任务结束,在执行下一个任务
      dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    • 异步执行(多线程的代名词):不需要前一个任务结束,就可以执行下一个任务
      dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

同步和异步的区别

同步执行:在当前线程中执⾏
异步执行:在另一条线程中执⾏

任务队列

串⾏队列、并发队列、主队列、全局队列

1、串⾏队列

  • GCD中获得串⾏行有2种途径
    dispatch_queue_t queue = dispatch_queue_create("name", NULL); // 创建
    dispatch_release(queue);// 非ARC需要释放⼿手动创建的队列

2、使⽤主队列 (跟主线程相关联的队列)

  • 主队列是GCD⾃自带的⼀种特殊的串⾏队列
  • 放在主队列中的任务,都会放到主线程中执⾏行

3、并发队列

GCD默认已经提供了全局的并发队列,供整个应用使用,不需要⼿手动创建
使用dispatch_get_global_queue函数获得全局的并发队列

dispatch_queue_t dispatch_get_global_queue( dispatch_queue_priority_t priority, unsigned long flags);

dispatch_queue_t queue = dispatch_get_global_queue(0,0);

全局并发队列的优先级

为了适配IOS7 和 IOS8 最近一段时间,优先级都写 0
● #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // ⾼高
● #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
● #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
● #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后 台

4、主队列

  • 所有任务都在主线程执行
  • 如果主线程当前有执行的任务,主队列中的任务不会调度
  • 等待主线程空闲后,主队列才会调度任务

使⽤用 dispatch_get_main_queue()

获得主队列 dispatch_queue_t queue = dispatch_get_main_queue();


GCD演练

// MARK: - 实例
- (void)gcdDemo1 {
    // 创建一个串行队列
    dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_SERIAL);

    // 创建一个block任务
    void (^task)() = ^ {
        NSLog(@"%@", [NSThread currentThread]);
    };

    // 设置执行模式:异步执行  同时:添加任务
    dispatch_async(q, task);
}


//===============================================================

// MARK: - 串行队列,同步执行
- (void)gcdDemo2 {

    // 创建一个串行队列
    dispatch_queue_t q = dispatch_queue_create("itcast", NULL);

    // 设置执行模式:同步执行  同时:添加任务
    for (int i = 0; i < 10; i++) {
        NSLog(@"%d---", i);
        // 上面的打印和第一句的添加任务到队列,都是在主线程上执行的
        dispatch_sync(q, ^ {
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }
    NSLog(@"come here");
}
// 结果是 1 2 3 4 在线程1 一次出来 ,最后输出 come here

//===============================================================

// MARK: - 串行队列,异步执行
- (void)gcdDemo3 {

    dispatch_queue_t q = dispatch_queue_create("itcast", NULL);

    for (int i = 0; i < 10; i++) {
        NSLog(@"%d---", i);
        dispatch_async(q, ^ {
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }
    NSLog(@"come 123here");
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"come here");
}
/*
 首先是通过循环添加任务到串行队列中
 0---
 1---
 2---
 3---
 4---
 5---
 6---
 7---
 8---
 9---

 因为是异步执行、然后就多线程执行(下面两个的输出顺序不确定)

 主线程输出:come here

 子线程输出:
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 0
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 1
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 2
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 3
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 4
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 5
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 6
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 7
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 8
 <NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 9
 */

//===============================================================


// MARK: - 并发队列,异步执行
- (void)gcdDemo4 {
    dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);  // COUCURRENT 是表示并发队列

    for (int i = 0; i < 10; i++) {

        dispatch_async(q, ^ {
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }

    NSLog(@"come here");
}
// 输出结果是:多线程输出,无顺序输出

//===============================================================

// MARK: - 并发队列,同步执行
- (void)gcdDemo5 {
    dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i < 10; i++) {
        NSLog(@"%d---", i);
        // 这里是上面输入下面输入交叉出现,说明同步执行的任务是添加一个,执行一个,只要有任务,就立即执行
        dispatch_sync(q, ^ {
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }

    NSLog(@"come here");
}
// 输出结果是:都在主线程,顺序输出,最后come here
// 都是主线程--是因为同步执行就在当前线程实行
// 顺序输出-- 虽然并发队列能多出,但是队列的特性是 先进先出

//===============================================================

// MARK: - 主队列,异步执行
- (void)gcdDemo6 {
    dispatch_queue_t q = dispatch_get_main_queue();

    for (int i = 0; i < 10; i++) {
        NSLog(@"%d---", i);
        dispatch_async(q, ^ {
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }

    NSLog(@"睡会");
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"come here");
}

/*
1---
2---
.
.
9---

睡会
come here
主队列是负责主线程上调度任务的,必须等主线程的语句执行完毕以后,在执行异步执行的子线程任务

任务顺序输出
 */

//===============================================================

// MARK: - 主队列,同步任务
- (void)gcdDemo7 {
    dispatch_queue_t q = dispatch_get_main_queue();
    NSLog(@"1---");

    dispatch_sync(q, ^{
        NSLog(@"来啊");
    });

    NSLog(@"come here");
}
// 这里会卡死
// 主队列要求:必须主线程执行完,才能执行调用任务
// 同步执行:不执行调用的任务,就不能往下走

//===============================================================

// MARK: - 主队列,同步任务,不死锁(不懂)
- (void)gcdDemo8 {
    dispatch_queue_t queue = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{

        dispatch_queue_t q = dispatch_get_main_queue();
        NSLog(@"1--- %@", [NSThread currentThread]);

        dispatch_sync(q, ^{
            [NSThread sleepForTimeInterval:6.0];
            NSLog(@"来啊 %@", [NSThread currentThread]);
        });

        NSLog(@"come here %@", [NSThread currentThread]);
    });

//    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"到底来不来");
}

// 到底来不来
// 1--- <NSThread: 0x7fbf895c9220>{number = 2, name = (null)}
// 来啊 <NSThread: 0x7fbf8940fe50>{number = 1, name = main}
// come here <NSThread: 0x7fbf895c9220>{number = 2, name = (null)}

// 整个是在 并发队列,异步执行 的大框架中
// 执行路径是  主队列任务--同步执行任务--主队列任务(确定的路径)
// 在这个框架里面,只要需要线程,系统就给提供,所以主队列和同步任务可以分别在两个不同的线程中执行
//===============================================================

#pragma mark - 同步任务的用处

 在网络开发中,通常会把很多任务放在后台异步执行,有的时候,有些任务会彼此有"依赖"关系!
 例子:小说的网站,用户登录,扣费,下载小说 A,扣费,下载小说 B...
 利用同步任务,能够做到,任务依赖关系,前一个同步任务不执行完,队列就不会调度后面的任务!

// MARK: - 同步任务的用处
- (void)gcdDemo9 {
    dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(q, ^{

        dispatch_sync(dispatch_get_main_queue(), ^ {
            NSLog(@"用户登录 %@", [NSThread currentThread]);
        });
        dispatch_async(dispatch_get_global_queue(0, 0), ^ {
            NSLog(@"下载 A %@", [NSThread currentThread]);
        });
        dispatch_async(q, ^ {
            NSLog(@"扣费 B %@", [NSThread currentThread]);
        });
    });
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"怎么能这样?");
}

// 如果把同步的“用户登录”的位置放在下载和扣费后面,就可能会出现下载  扣费 再登录。如果在用户登录前睡三秒,则肯定会下载  扣费  用户登录  怎么能这样
// 猜测是,主线程只要有任务,就会执行任务,不调度其他线程的任务,所以如果放在下载和扣费后面,在主队类添加任务之前还睡了已汇入,主队列没有添加任务,
// 睡觉的时候,下载和扣费就已经添加到并发队列并异步执行了,因为此时主队列中没有任务 ,就不会阻碍调度,这样就会先输出下载和扣费,


//===============================================================


#pragma mark - 全局队列
// MARK: 全局队列(本质上就是并发队列)
//
- (void)gcdDemo9 {
    /**
     参数
     第一个参数 1. 涉及到系统适配
        iOS 8       服务质量(让线程响应的更快还是更慢)
         - QOS_CLASS_USER_INTERACTIVE           用户交互(用户迫切希望线程快点被执行,不要用耗时的操作)
         - QOS_CLASS_USER_INITIATED             用户需要的(同样不要使用耗时操作)
         - QOS_CLASS_DEFAULT                    默认的(给系统用来重置队列的)
         ** QOS_CLASS_UTILITY                    实用工具(用来做耗时操作)
         - QOS_CLASS_BACKGROUND                 后台
         - QOS_CLASS_UNSPECIFIED                没有指定优先级

        iOS 7       调度的优先级
         - DISPATCH_QUEUE_PRIORITY_HIGH 2       高优先级
         - DISPATCH_QUEUE_PRIORITY_DEFAULT 0    默认优先级
         - DISPATCH_QUEUE_PRIORITY_LOW (-2)     低优先级
         - DISPATCH_QUEUE_PRIORITY_BACKGROUND   后台优先级

     提示:尤其不要选择 BACKGROUND 优先级和服务质量,用户不需要知道线程什么时候执行完成!线程的执行会慢的令人发指!

     有关服务质量的介绍,用在与 XPC 框架结合使用的,XPC 用在 MAC 平台上做进程间通讯的框架!

     因为大家工作后,暂时会考虑 iOS7 & iOS8 的适配,无法使用服务质量,直接指定 0,能够做到 iOS7 & 8 的适配
     dispatch_get_global_queue(0, 0);

     2. 为未来使用保留的,应该始终传入0
     */
    dispatch_queue_t q = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);

    for (int i = 0; i < 10; i++) {
        dispatch_async(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
    NSLog(@"com here");
}

全局队列和并发队列的区别

全局队列 & 并发队列
1> 名称,并发队列有名称,适合于商业级软件跟踪错误报告!
2> release, 在 MRC 开发时,并发队列需要使用
dispatch_release(q);
结论:目前绝大多数的软件都会使用全局队列。比较优秀的第三方框架会使用自定义的并发队列!
日常使用:用全局队列!

全局队列和串行队列的选择

全局队列:
- 并发,能够调多个线程,效率高
- 费电,费流量

串行队列
- 如果任务之间需要依赖(ex登陆,下载,扣费),使用串行队列或者主队列
- 省电,省钱,省流量

所以选择的依据是:
- WIFI的情况下,有电,有流量,可以多开线程 (6条)
- 3G/4G蜂窝网络情况下尽量少开,2-3条线程即可
- 时刻替用户省钱省电


延时执行

#pragma mark - 延时执行
- (void)delay {
    NSLog(@"come here");

    /**
     参数:
     从现在开始,经过多少纳秒之后,让 queue 队列,调度 block 任务,异步执行!

     1. when
     2. queue
     3. block(dispatch_block 是 iOS 8.0推出的)
     */
    // 从现在开始,经过多少纳秒之后
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));

    // 主队列
//    dispatch_after(when, dispatch_get_main_queue(), ^{
//        NSLog(@"%@", [NSThread currentThread]);
//    });
    // 全局队列
//    dispatch_after(when, dispatch_get_global_queue(0, 0), ^{
//        NSLog(@"%@", [NSThread currentThread]);
//    });
    // 串行队列
    dispatch_after(when, dispatch_queue_create("itcast", NULL), ^{
        NSLog(@"%@", [NSThread currentThread]);
    });
}

一次性执行

有的时候,我们在开发中,有些代码,从程序启动之后,就只希望执行一次!
尤其在单例设计模式中使用非常普遍,在iOS 开发中,单例的使用,已经到了“滥用”程度!

一次性执行是在当前线程执行的。不会另外分出线程来执行

就算在多线程中测试一次性执行,仍然只是执行一次就不再执行了。

通过在互斥锁之前添加判断,也能达到一次性执行的目的,但是效率相比一次性执行要低很多很多。所以不推荐使用互斥锁

#pragma mark - 一次性执行
- (void)once {
    NSLog(@"来了");

    // 苹果提供了一个一次行执行的机制,不仅能够保证只被执行一次,而且是"线程安全"的
    // 苹果推荐使用 dispatch_once_t 来做一次性执行!因为效率高!
    // 不要使用互斥锁,互斥锁效率低!
    static dispatch_once_t onceToken;
    NSLog(@"%ld", onceToken);

    dispatch_once(&onceToken, ^{
        // 只会执行一次的代码
        NSLog(@"执行了!%@", [NSThread currentThread]);
    });
}



@implementation ViewController

long largeNmuber = 1000 * 1000;

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"%.04f", [self syncDemo1]);
    NSLog(@"%.04f", [self onceDemo1]);

    NSLog(@"%.04f", [self syncDemo2]);
    NSLog(@"%.04f", [self onceDemo2]);
}
//=================================================================

#pragma mark - 单线程测试
- (CFAbsoluteTime)syncDemo1 {
    CFAbsoluteTime start =  CFAbsoluteTimeGetCurrent();
    // 单线程测试互斥锁
    for (int i = 0; i < largeNmuber; ++i) {
        [DemoObj syncDemoObj];
    }
    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
    return end - start;
}

- (CFAbsoluteTime)onceDemo1 {
    CFAbsoluteTime start =  CFAbsoluteTimeGetCurrent();
    // 单线程测试一次性执行
    for (int i = 0; i < largeNmuber; ++i) {
        [DemoObj onceDemoObj];
    }
    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
    return end - start;
}

最后的输出结果是,可以对比出效率来

    单线程互斥锁 0.6975
    单线程一次性 0.1761

    多线程互斥锁 6.2929
    多线程一次性 0.2352
//=================================================================
#pragma mark - 多线程测试
- (CFAbsoluteTime)syncDemo2 {
    CFAbsoluteTime start =  CFAbsoluteTimeGetCurrent();

    dispatch_group_t group = dispatch_group_create();

    for (int i = 0; i < 10; i++) {
        dispatch_group_enter(group);
        // 异步执行 测试互斥锁
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int j = 0; j < largeNmuber / 10; j++) {
                [DemoObj syncDemoObj];
            }
            dispatch_group_leave(group);
        });
    }

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
    return end - start;
}


- (CFAbsoluteTime)onceDemo2 {
    CFAbsoluteTime start =  CFAbsoluteTimeGetCurrent();

    dispatch_group_t group = dispatch_group_create();

    for (int i = 0; i < 10; i++) {
        dispatch_group_enter(group);

        // 异步执行 测试  一次性执行
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int j = 0; j < largeNmuber / 10; j++) {
                [DemoObj onceDemoObj];
            }
            dispatch_group_leave(group);
        });
    }

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
    return end - start;
}
@end

调度组

在实际开发中,有时候需要监听多个异步执行的任务的完成情况。等到所有的任务都完成之后再通知,可以用调度组实现

Ex: 下载三个电影,等所有的电影都下载完以后,在通知用户

1、创建队列
2、创建调度组
3、给调度组添加任务
4、所有任务执行完毕通知

注意:一般下载任务都是放在后台线程进行的,但是要把调度组的通知动作放到主线程,因为下载完以后进行通知会有UI的更新

调度组形式一

- (void)group1 {
    // 1. 队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // 2. 调度组
    dispatch_group_t g = dispatch_group_create();

    // 3. 添加任务,让队列调度,指定任务执行函数,最终通知群组
    dispatch_group_async(g, q, ^{
        NSLog(@"download A %@", [NSThread currentThread]);
    });
    dispatch_group_async(g, q, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"download B %@", [NSThread currentThread]);
    });
    dispatch_group_async(g, q, ^{
        [NSThread sleepForTimeInterval:0.8];
        NSLog(@"download C %@", [NSThread currentThread]);
    });

    // 4. 所有任务执行完毕后,获得通知
    // 用一个调度组,可以监听全局队列调度的任务,执行完毕后,在主队列执行最终处理!
    // dispatch_group_notify 本身是异步的
    dispatch_group_notify(g, dispatch_get_main_queue(), ^{
        // 更新UI,通知用户!
        NSLog(@"OK %@", [NSThread currentThread]);
    });

    NSLog(@"come here");


// 如果下载完以后,还需要做其他的动作,就不要调用调度组自带的通知动作,课用下面代码实现
// 等待到永远,死等,阻塞住线程执行,一直到所有的任务执行完毕,才会执行后续的代码!
    dispatch_group_wait(g, DISPATCH_TIME_FOREVER);

    NSLog(@"全都完毕");


}


调度组形式二


钓鱼度的任务添加更加灵活

- (void)group2 {
    // 1. 队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // 2. 调度组
    dispatch_group_t g = dispatch_group_create();

    // 3. 进入群组,执行此函数后,再添加的异步执行的block,会被group监听
    // dispatch_group_enter & dispatch_group_leave一定要配对出现
    dispatch_group_enter(g);

    // 4. 添加任务
    dispatch_async(q, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"download A");
        // 异步任务中,所有的代码执行完毕后,最后离开群组
        dispatch_group_leave(g);
    });

    // 再次添加任务
    dispatch_group_enter(g);

    // 5. 添加任务 B
    dispatch_async(q, ^{
        NSLog(@"download B");
        // 异步任务中,所有的代码执行完毕后,最后离开群组
        dispatch_group_leave(g);
    });

    // 6. 拦截通知,调度组执行完以后,调用此通知
//    dispatch_group_notify(g, q, ^{
//        NSLog(@"Over");
//    });
    // 等待到永远,死等,阻塞住线程执行,一直到所有的任务执行完毕,才会执行后续的代码!
    dispatch_group_wait(g, DISPATCH_TIME_FOREVER);

    NSLog(@"全都完毕");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值