012-GCD多线程技术

2017-11-26  本文已影响19人  Yasic

多线程

线程是进程内部执行任务的一种途径,多线程技术能适当提高程序执行效率和资源利用率,iOS 中的多线程技术主要有以下几种

多线程的创建是需要资源开销的,同时维护和调度线程也需要开销,程序设计和线程间通信也会因线程数目增多而变得复杂。
iOS 的主线程是在一个应用启动后默认开启的,主要负责显示、刷新和处理 UI,因此不能把比较耗时的任务放在主线程完成,会带来 UI 卡顿问题。
同时多线程也会带来线程安全的问题,当多个线程同时访问同一个对象或是存储空间时,由于读写操作的非原子性或是线程间的协同不够,就会带来严重的数据丢失或错乱问题,因此需要对资源进行同步加锁操作。

@synchronized(锁对象) { // 需要锁定的代码  };

当然在定义属性的时候也可以通过设置 atomic 特性来为属性的读写方法加锁。

GCD

GCD 是 iOS 用来管理多线程的技术,它会自动利用更多 CPU 内核,自动管理线程生命周期,使得使用线程变得更加轻便简洁。

任务和队列

GCD 中加入了两个重要的概念,任务和队列。

在 GCD 中任务表现为一个个 block,包含需要执行的代码,任务的执行分两种,同步执行和异步执行,同步执行会阻塞当前线程,异步执行会创建新线程,不会阻塞当前线程。

dispatch_async 方法会异步执行任务,这意味着它不会阻塞当前线程

-(void)func{
    dispatch_async(qQueue, ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

这里 1 和 2 的打印顺序不固定,因为 async 执行任务不会阻塞,所以当前线程会继续向下执行。

dispatch_async 方法会同步执行任务,意味着当前线程一直阻塞到它完成任务才会继续执行

-(void)func{
    dispatch_sync(qQueue, ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

这里打印顺序一定是先 1 后 2 的。

队列用于存放任务,然后由 Run Loop 从中取出任务分发给各个线程。队列也分为串行队列和并行队列。串行队列会按照 FIFO 顺序被执行,并行队列则会并行执行,并行队列只能在异步函数中起作用。

创建队列

主队列是一个特殊的串行队列,放在主队列的任务都会放到主线程执行

dispatch_queue_t queue = dispatch_get_main_queue();

全局并行队列是系统提供的并行队列,无需手动创建。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

这里第二个 flag 参数是保留位,使用时要置0,第一个参数表示线程优先级,也就是说有4个可选的全局并行队列,优先级分别是

#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
// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("serial_queue", NULL);
dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

自定义队列的优先级有两种定义方法

这个方法还可以设置队列层次结构,当我们想让不同队列的任务同步执行时,可以创建一个串行队列,将其他队列的 target 设置为这个队列,就可以实现了。

    dispatch_queue_t targetQueue = dispatch_queue_create("myqueue", NULL);
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_set_target_queue(targetQueue, globalQueue);

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_async(queue1, ^{
        NSLog(@"queue1 1");
    });
    dispatch_async(targetQueue, ^{
        NSLog(@"async");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 1");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 2");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 3");
    });
    dispatch_async(queue1, ^{
        NSLog(@"queue1 2");
    });
    dispatch_async(queue1, ^{
        sleep(1);
        NSLog(@"queue1 3");
    });

这样执行的结果如下

queue1 1
queue1 2
queue1 3
async
queue2 1
queue2 2
queue2 3

简单来说,设置了 target 以后,并不是按照设置的队列顺序来执行任务的,而是按照分发任务的队列顺序来执行,如果先设置了 queue1 的任务,就会将 queue1 的任务执行完再执行其他队列。

分发任务

dispatch_async

前面提到过,这个方法是异步执行代码块中任务。

dispatch_sync

这个方法会同步执行任务,执行顺序可以预测,但是要注意使用不当可能会造成死锁。

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"123");
    });
NSLog(@"456");

在这里,dispatch_sync 由于是在主线程运行,因此会阻塞主线程,一直等到 block 中的代码执行返回后才会继续执行,但是 block 又是在主线程执行的,因为主线程被阻塞所以不会执行 block,于是两者相互等待,就发生了死锁。

once

dispatch_once 保证 block 只会被执行一次,一般用于单例模式中初始化 static 的单例对象。

    static dispatch_once_t onceToken;
    NSLog(@"%ld", onceToken);
    dispatch_once(&onceToken, ^{
        
    });
    NSLog(@"%ld", onceToken);

打印结果可以看到 onceToken 一开始是 0,执行完以后会变成 -1,从而标识这个 block 已被执行过。

apply

dispatch_apply 这个方法会循环执行任务,指定循环次数就会在 queue 中将 block 循环执行指定次数。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(5, queue, ^(size_t i) {
        if (i == 3)
        {
            sleep(1);
        }
        NSLog(@"%zu", i);
    });

当然至于是串行执行还是并行执行则要看队列的属性。

group

group 是一组执行的任务,适用于需要等待一组任务完成触发其他代码的场景。

after

dispatch_after 会在指定时间后将任务加入到指定队列中,当然不代表会立刻执行此任务。

    dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) 3 * NSEC_PER_SEC);
    dispatch_after(time, queue, ^{
        NSLog(@"after 3 second");
    });

barrier

dispatch_barrier_async 用于等待前面的任务执行完毕后自己才执行,而它后面的任务需等待它完成之后才执行。还有个串行函数 dispatch_barrier_sync 是会阻塞当前线程等待指定队列完成任务再继续执行的。

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
    
    dispatch_async(queue1, ^{
        NSLog(@"1 1");
    });
    
    dispatch_async(queue1, ^{
        sleep(1);
        NSLog(@"1 2");
    });
    
    dispatch_barrier_sync(queue1, ^{
        NSLog(@"1 3");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"2 1");
    });

打印结果是

1 1
1 2
1 3
2 1

如果是执行的 dispatch_barrier_async 则 "2 1" 不会最后才打印。

block

可以看到插入到队列的任务一般就是 block 代码块中的代码,也可以自己定义一个 block 进行复用。

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
    
    dispatch_block_t block1 = dispatch_block_create(0, ^{
        NSLog(@"block1");
    });
    
    dispatch_block_t block2 = dispatch_block_create(0, ^{
        NSLog(@"block2");
    });
    
    dispatch_async(queue1, block1);
    dispatch_async(queue2, block2);
上一篇下一篇

猜你喜欢

热点阅读