GCD使用(多线程004)


1. RunLoop介绍

  • Runloop被称为消息循环或事件循环
  • 每个线程里,都有一个消息循环
  • 默认情况下,主线程开启消息循环,子线程不开启

目的

  • 保证程序不退出
  • 负责处理输入事件
  • 如果没有事件处理,会让程序进行休眠

消息类型(事件类型)

  • Input Sources(输入源)

Input for sources such as mouse and keyboard events from the window system, NSPort objects, and NSConnection objects.

包括键盘鼠标事件,NSPort,NSConnection,等。

  • Timer Sources(定时源)

An NSRunLoop object also processes NSTimer events.

就是定时器NSTimer

常用的循环模式

  • NSDefaultRunLoopMode

The mode to deal with input sources other than NSConnection objects. This is the most commonly used run-loop mode. Available in iOS 2.0 and later.

  • NSRunLoopCommonModes

Objects added to a run loop using this value as the mode are monitored by all run loop modes that have been declared as a member of the set of “common" modes; see the description of CFRunLoopAddCommonMode for details.

如何使用

  1. 创建消息
  2. 把消息加入到消息循环中,并指定循环的模式

加入的2种方法

[self performSelector:@selector(demo2) onThread:thread withObject:nil waitUntilDone:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

只有当消息的循环模式和当前线程中消息循环的模式像符合,消息才能被运行

子线程中的消息循环

子线程中默认不开启消息循环

开启方式

  1. Run方法

    [[NSRunLoop currentRunLoop] run];
    

    缺点:开启之后无法关闭,并且在他之后的代码都不会被运行,因为他是一个死循环

  2. runUntilDate

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
  1. 苹果推荐方法
    BOOL shouldKeepRunning = YES;        // global
     NSRunLoop *theRL = [NSRunLoop currentRunLoop];
     while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    

问题演示

  • 线程没有执行方法,线程立马销毁
NSThread *thread = [[NSThread alloc] init];
[thread start];
[self performSelector:@selector(demo2) onThread:thread withObject:nil waitUntilDone:NO];
  • 线程指定方法,但是没有开启消息循环
 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    [thread start];
    [self performSelector:@selector(demo2) onThread:thread withObject:nil waitUntilDone:NO];

    ..........................

    - (void)demo {
    NSLog(@"over");
}

2. GCD简介

  • 全称是Grand Central Dispatch
  • 纯C语言,提供了非常多强大的函数

优点

  • GCD是苹果公司为多核的并行运算提出的解决方案
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

核心组成部分

任务&队列

任务:要干什么事情

队列:用来存放任务

如何做

  1. 确定要做的事情
  2. 把任务加到队列里
  3. 没了

GCD会自动将任务从队列里面取出,放到对应的贤臣各种去执行

任务取出的原则是先进先出FIFO(First in first out)

举例

//任务
dispatch_block_t block;
//队列
dispatch_queue_t queue;
//把任务放到队列里
dispatch_async(queue, block);

3. 队列执行任务的方式

同步执行任务

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务

异步执行任务

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

区别

同步:在当前线程上执行 异步:在其他线程上执行


4. 队列的种类

并发队列(Concurrent Dispatch Queue)

  • 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
  • 并发功能只有在异步(dispatch_async)函数下才有效
dispatch_queue_t queue = dispatch_queue_create("CONCURRENT", DISPATCH_QUEUE_CONCURRENT);

串行队列(Serial Dispatch Queue)

  • 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
dispatch_queue_t queue = dispatch_queue_create("SERIAL", DISPATCH_QUEUE_SERIAL);

5. 同步、异步、并发、串行的基本概念

同步和异步决定了要不要开启新的线程

  • 同步:在当前线程中执行任务,不具备开启新线程的能力
  • 异步:在新的线程中执行任务,具备开启新线程的能力

并发和串行决定了任务的执行方式

  • 并发:多个任务并发(同时)执行
  • 串行:一个任务执行完毕后,再执行下一个任务

6. 串行队列的执行

串行队列的同步执行

  • 不开线程,同步执行(在当前线程执行)
#define DISPATCH_QUEUE_SERIAL NULL
    //串行队列
    //dispatch_queue_t q = dispatch_queue_create("test", NULL);
    dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        //同步执行
        dispatch_sync(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }

串行队列的异步执行

  • 开一个线程,顺序执行
 //只有一个线程,因为是串行队列,只有一个线程就可以按顺序执行队列中的所有任务
    dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        //异步执行
        dispatch_async(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }

7. 并行队列的执行

并行队列,异步执行

  • 开多个线程,异步执行,每次开启多少个线程是不固定的(线程数,不由我们控制),线程数是由gcd来决定的
    dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        //异步执行
        dispatch_async(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }

并行队列,同步执行

  • 不开线程,顺序执行,与串行队列的同步执行一模一样

8. 系统提供的队列

iOS已经为我们准备好了2个常用队列主队列列和全局并发队列,开发中用的很多

主队列

  • 主队列是负责在主线程调度任务的
  • 会随着程序启动一起创建
  • 主队列只需要获取不用创建
    dispatch_queue_t queue = dispatch_get_main_queue();
    

主队列,异步任务

  • 不开线程,同步执行
  • 主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后在执行任务
  • 主队列又叫 全局串行队列

主队列,同步执行

  • 程序执行不出来(死锁)死锁的原因

当程序执行到下面这段代码的时候

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

主队列:如果主线程正在执行代码,就不调度任务

同步执行:如果第一个任务没有执行,就继续等待第一个任务执行完成,再执行下一个任务此时互相等待,程序无法往下执行(死锁)

全局并发队列

  • 为了方便程序员开发提供的队列,与并发队列行为相同

9. 同步异步串行并发的组合结果

 全局并发队列手动创建串行队列主队列
同步(sync)没有开启新线程,串行执行任务没有开启新线程,串行执行任务死锁
异步(async)有开启新线程,并行执行任务开启新线程,并行执行任务没有开启新线程,串行执行任务

10. GCD延时函数使用

dispatch_after的定义

dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);

dispatch_after的参数

  • 参数1 dispatch_time_t when

多少纳秒之后执行

  • 参数2 dispatch_queue_t queue

任务添加到那个队列

  • 参数3 dispatch_block_t block

要执行的任务

例子

//延时操作
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    });

11. GCD一次执行和单例实现

有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”

// MARK: 一次性执行
- (void)once {
    static dispatch_once_t onceToken;
    NSLog(@"%ld", onceToken);

    dispatch_once(&onceToken, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"一次性吗?");
    });
    NSLog(@"come here");
}
  • dispatch_once是线程安全的,因为内部也有一把锁,苹果推荐使用该种方式来实现单例模式

测试

单例的2中实现

  • 使用 dispatch_once 实现单例
+ (instancetype)sharedSingleton {
    static id instance;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });

    return instance;
}
  • 使用互斥锁实现单例
+ (instancetype)sharedSync {
    static id syncInstance;

    @synchronized(self) {
        if (syncInstance == nil) {
            syncInstance = [[self alloc] init];
        }
    }

    return syncInstance;
}
  • 对比结果
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    long largeNumber = 1000 * 1000;

    // 测试互斥锁
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    for (long i = 0; i < largeNumber; ++i) {
        [Singleton sharedSync];
    }
    NSLog(@"互斥锁: %f", CFAbsoluteTimeGetCurrent() - start);

    // 测试 dispatch_once
    start = CFAbsoluteTimeGetCurrent();
    for (long i = 0; i < largeNumber; ++i) {
        [Singleton sharedSingleton];
    }
    NSLog(@"dispatch_once: %f", CFAbsoluteTimeGetCurrent() - start);
}

12. GCD任务组

使用场景:在组里的异步任务都执行完毕后,再去执行其他操作,例如下载歌曲,等所有的歌曲都下载完毕之后 转到 主线程提示用户

  • 方式一:dispatch_group_async常用用法
/**
 调度组-在一组异步代码执行完毕后,统一获得通知

 应用场景:将一组图像异步缓存到本地之后统一获得通知!
 */
- (void)group1 {
    // 队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 调度组
    dispatch_group_t group = dispatch_group_create();

    // 添加异步
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"download A %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"download B %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"download C %@", [NSThread currentThread]);
    });

    // 调度组通知 - 监听群组中,所有异步执行的代码完成后,得到通知
    // 异步监听!
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"下载完成 %@", [NSThread currentThread]);
    });

    NSLog(@"come here");
}
  • 方式二:添加enter & leave
// MARK: - 调度组 2
- (void)group2 {
    // 1. 调度组
    dispatch_group_t group = dispatch_group_create();

    // 2. 队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // dispatch_group_enter & dispatch_group_leave 必须成对出现
    dispatch_group_enter(group);
    dispatch_group_async(group, q, ^{
        NSLog(@"任务 1 %@", [NSThread currentThread]);

        // dispatch_group_leave 必须是 block 的最后一句
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_group_async(group, q, ^{
        NSLog(@"任务 2 %@", [NSThread currentThread]);

        // dispatch_group_leave 必须是 block 的最后一句
        dispatch_group_leave(group);
    });

    // 4. 阻塞式等待调度组中所有任务执行完毕
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    // 5. 判断异步
    NSLog(@"OVER %@", [NSThread currentThread]);
}

注意:不要跟dispatch_barrier_async混淆,一个是所有任务都完成后,一个是某个任务中的一段代码需要交给另一个线程有序的串行执行


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值