iOS GCD

GCD简介

GCD 是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快。通过 GCD,开发者只需要向队列中添加一段代码块,而不需要直接和线程打交道。GCD在后端管理着一个线程池,它不仅决定着你的代码块将在哪个线程被执行,还根据可用的系统资源对这些线程进行管理。这样通过GCD来管理线程,从而解决线程被创建的问题。

GCD基本概念

任务和队列

名词说明
线程程序执行任务的最小调度单元
任务一段代码:GCD中,block中需要执行的代码
队列存放任务的数组,FIFO(先进先出)的原则

异步、同步,并行和串行

名词说明
异步(async)具备开新线程的能力
同步(sync)不具备开新线程的能力
并行任务可以并发执行
串行任务按顺序执行

说明:使用GCD开启多线程执行多个任务时,需具备两个条件:

1、能开启新线程
2、任务可以并发执行

即:"异步"+"并行"

GCD几种组合

-并行队列串行队列主队列
异步开启新线程,任务同时执行(1)开启新线程,任务顺序执行(2)不开新线程,任务顺序执行(5)
同步不开启新线程,任务顺序执行(3)不开启新线程,任务顺序执行(4)主线程中:死锁。子线程中:不开启新线程,任务顺序执行(6)

详细说明:
(1)异步使得队列开启了新的线程,并行队列让任务可以同时执行(常用)
(2)虽然开启了新线程,但是队列调度方式是串行的,因此任务只能顺序执行
(3)同步意味着不能开启新线程,虽然是并行队列,但线程只有一个,因此任务只能顺序执行
(4)不能开新线程,任务队列是串行,任务顺序执行

总结:正如上面说到,使用GCD完成多线程多任务时,需要具备两个能力:开启新线程的能力,任务可以同时执行的能力,即"异步"+“并行”。两者缺一不可,缺少任何一个,队列里的任务都是顺序执行。

接下来说明一下主队列:主队列其实是一个串行队列

(5)主队列里的任务都是在主线程中完成的,即使使用异步(async),也不会开启新线程,并且主队列是一个串行队列,任务顺序执行。
(6)在主线程中出现死锁,是因为任务被加到主队列中,想要被执行block中的代码必须等到主线程上的任务都执行完毕,但是,因为是同步任务,想要主线程上的任务执行完毕,势必需要执行任务中的block中的代码,因此两者相互等待,出现死锁;但是如果在子线程中添加同步任务,并不会阻塞主线程上的任务执行完毕,因此结果会和"同步"+"串行"一致。

GCD的基本使用

GCD使用步骤分两步:

1、获取一个队列
2、将任务添加到队列中

系统会自动调度任务,通常是FIFO(先来先服务)

获取队列

// DISPATCH_QUEUE_SERIAL 串行
// DISPATCH_QUEUE_CONCURRENT 并行
dispatch_queue_create("队列标识符",队列类型);

我们可以使用 dispatch_queue_create 来创建队列,队列类型有两种:DISPATCH_QUEUE_SERIAL串行队列,DISPATCH_QUEUE_CONCURRENT并行队列。

GCD为我们提供了两种快捷获取队列的方式,一个是主列队(串行队列),一种是全局队列(并行队列)

1、获取主队列

dispatch_get_main_queue();

主队列中任务都会放到主线程中执行,并且是顺序执行。

2、获取全局并发队列

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

第一个参数是队列的优先级,一般选择默认即可;
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台

创建任务的方法

GCD为我们提供了创建同步和异步的方法,分别是dispatch_syncdispatch_async,其中第一个参数是队列,第二个是需要执行的block块,我们的任务就放在这里

// 创建同步任务
dispatch_sync(queue, ^{
    // 同步任务
});
// 创建异步任务
dispatch_async(queue, ^{
    // 异步任务
});

GCD线程通信

在开发过程中,我们通常将一些耗时的操作放在子线程,如数据请求,文件下载等,当这些任务完成后,我们需要即使的更新到UI上,这时我们就需要回到主线程

//获取【并行】队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建【异步】任务
dispatch_async(queue, ^{
    // do something ...
    [NSThread sleepForTimeInterval:2.0];
    // 回到主线程
    dispatch_queue_t main = dispatch_get_main_queue();
    dispatch_async(main, ^{
        // 更新UI
    });
});

整个过程如上述代码所示,我们获取并行队列,创建异步任务,但完成耗时操作后,再获取到主线程,将更新UI的任务添加到主线程上去。

dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(main, ^{
    // 更新UI
});

验证组合

(1)【异步】+【并行】

NSLog(@"current thread:%@",[NSThread currentThread]);
// 创建【并行】队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
// 开启多个【异步】任务
dispatch_async(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"1:%@",[NSThread currentThread]);
    }
});
dispatch_async(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"2:%@",[NSThread currentThread]);
    }
});
dispatch_async(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"3:%@",[NSThread currentThread]);
    }
});
NSLog(@"end");

结果

我们发现,"end"在线程输出之前,说明当前线程并未等待,而是开启了新的线程执行任务(3、4、5号线程);任务1、2、3交替完成,说明并发队列在同时执行多个任务。

(2)【异步】+【串行】

我们将【并行】队列改为【串行】队列。

NSLog(@"current thread:%@",[NSThread currentThread]);
// 创建【串行】队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
// 开启多个【异步】任务
dispatch_async(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"1:%@",[NSThread currentThread]);
    }
});
dispatch_async(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"2:%@",[NSThread currentThread]);
    }
});
dispatch_async(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"3:%@",[NSThread currentThread]);
    }
});
NSLog(@"end");

结果

我们发现,虽然开启了新的线程,但是队列是一个串行队列,因此任务是顺序执行的:1->2->3,另外,我们注意到,串行队列下,dispatch_async只会开启一个线程。

(3)【同步】+【并行】

我们再稍稍改动代码,从而产生并行队列,同步任务。

NSLog(@"current thread:%@",[NSThread currentThread]);
// 创建【并行】队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
// 开启多个【同步】任务
dispatch_sync(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"1:%@",[NSThread currentThread]);
    }
});
dispatch_sync(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"2:%@",[NSThread currentThread]);
    }
});
dispatch_sync(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"3:%@",[NSThread currentThread]);
    }
});
NSLog(@"end");

结果

我们可以看到,虽然是并行队列,但是dispatch_sync同步的条件使得任务并没有开启新的线程,而是在当前线程(例子中是主线程)执行,并且按照顺序执行,另外,我们可以看到,"end"是在最后才输出,这就是同步的原因。

(4)【同步】+【串行】

NSLog(@"current thread:%@",[NSThread currentThread]);
// 获取【串行】队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
// 开启多个【同步】任务
dispatch_sync(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"1:%@",[NSThread currentThread]);
    }
});
dispatch_sync(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"2:%@",[NSThread currentThread]);
    }
});
dispatch_sync(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"3:%@",[NSThread currentThread]);
    }
});
NSLog(@"end");

结果

我们发现,同步+串行和同步+并行的结果是一致的。

最后,我们来看下,比较特殊的串行队列的主队列

(5)【异步】+【主队列】

NSLog(@"current thread:%@",[NSThread currentThread]);
// 获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 开启多个【异步】任务
dispatch_async(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"1:%@",[NSThread currentThread]);
    }
});
dispatch_async(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"2:%@",[NSThread currentThread]);
    }
});
dispatch_async(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"3:%@",[NSThread currentThread]);
    }
});
NSLog(@"end");

结果

我们发现,结果似乎和【异步】+【串行】一样,其实主队列就是一种串行队列,不同的是,主队列并不会开启新的线程,所有的任务都会放在主线程中完成,并且服从FIFO先来先服务的原则

(6)【同步】+【主队列】

NSLog(@"current thread:%@",[NSThread currentThread]);
// 获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 开启多个【同步】任务
dispatch_sync(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"1:%@",[NSThread currentThread]);
    }
});
dispatch_sync(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"2:%@",[NSThread currentThread]);
    }
});
dispatch_sync(queue, ^{
    for (int i=0; i<2; i++) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"3:%@",[NSThread currentThread]);
    }
});
NSLog(@"end");

结果

呃呃。。。我们发现,在主线程中,【同步】+【主队列】的方式发生了死锁,这个是因为dispatch_sync将任务添加到主队列中,任务block部分需要等待主线程上的任务执行完毕之后才会执行,但是由于dispatch_sync会阻塞当前线程,直到之前的任务都完成才会继续执行,这导致主线程的任务永不能完成,任务block里的代码也用不能被执行,从而产生了死锁。

既然dispatch_sync会阻塞当前线程,那我们将其放在子线程中会怎么样呢?

我们开启一个子线程测试

[NSThread detachNewThreadSelector:@selector(mainThreadVSSync) toTarget:self withObject:nil];

在子线程任务中,重新执行【同步】+【主队列】任务

-(void)mainThreadVSSync{
    NSLog(@"current thread:%@",[NSThread currentThread]);
    // 获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    // 开启多个【同步】任务
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"1:%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"2:%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"3:%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end");
}

结果

我们发现,程序可以继续执行。我们使用【同步】+【主队列】时,线程不在是主线程,而是3号线程,这样dispatch_sync开启同步任务时,并不会影响到主线程,同步任务可以继续执行,只不过都是在主线程中,而且是顺序执行。

总结

1、GCD方式实现多线程多任务必须是【异步】+【并行队列】,缺一不可,其他方式的任务都是顺序执行的,无论异步还是同步。

2、主队列是一种串行队列,切忌在主线程中使用【同步】+【主队列】的方式开启任务,会出现死锁。


GCD其他

队列组:dispatch_group和dispatch_group_notify

有时,我们需要开启多个异步任务,并且所有任务都结束之后,再回到主线程执行任务,那么该如何做呢?这里我们就需要dispatch_group了。

步骤:创建group->关联任务、队列、group->接受group通知

a、我们通过dispatch_group_create()创建一个队列组

b、使用dispatch_group_async方法,将任务放在队列中,队列则关联到group

c、接收完成通知dispatch_group_notify

// 创建一个队列组
dispatch_group_t group = dispatch_group_create();
// 获取一个【并行】队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 使用队列组发起一个耗时任务
dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"1:%@",[NSThread currentThread]);
    }
});
// 另外一个耗时任务
dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"2:%@",[NSThread currentThread]);
    }
});
// 完成通知,在主队列中完成UI更新
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"更新UI操作");
    NSLog(@"%@",[NSThread currentThread]);
});

结果

我们可以看到,只有当多线程中多任务完成后,dispatch_group_notify中更新UI的操作才会被执行

dispatch_group_wait

当方法会阻塞当前线程,等待指定当group中当任务执行完成后,才会继续执行。

1中的例子,也可以使用该方法达到同样的效果

	// 创建一个队列组
    dispatch_group_t group = dispatch_group_create();
    // 获取一个【并行】队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 使用队列组发起一个耗时任务
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"1:%@",[NSThread currentThread]);
        }
    });
    // 另外一个耗时任务
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"2:%@",[NSThread currentThread]);
        }
    });
    // 阻塞当前线程
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"更新UI操作");
    NSLog(@"%@",[NSThread currentThread]);

结果

但是,需要注意的是,dispatch_group_wait是会阻塞当前线程的,而dispatch_group_notify则不会。

延迟执行:dispatch_after

我们可以使用GCD快速的创建一个延迟执行的任务。当然,由于是添加到主队列的中的,因此这个延迟的时间是不准确的,这里还包括了队列前的任务时长。

NSLog(@"执行前:%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"执行时:%@",[NSThread currentThread]);
});

结果

我们发现,时差为2.2的左右,是大于我们所设的2.0秒的。

一次性代码:dispatch_once

GCD可以创建一次性代码,在制作单例时,我们常常使用到它dispatch_once,该函数可以保证某段代码在程序中只执行1次,并且在多线程的环境下,也可以保证线程安全。

-(void)onceTask{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
	    // 一次性任务
    });
}
快速迭代方法:dispatch_apply

通常我们会使用for循环遍历数组,GCD中的dispatch_apply提供了类似的方法,不同的是,dispatch_apply不仅可以是顺序的遍历,还可以是并发的遍历,主要看队列是串行的还是并行的。

NSLog(@"apply---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");

并行迭代

我们发现遍历并不是顺序执行的,如果使用的谁串行队列,和使用for循环遍历是一样的效果。

信号量:dispatch_semaphore

信号量类似生活当中的信号灯,红灯停,绿灯行。GCD中的信号量Dispatch Semaphore是持有计数的信号,计数为0时等待,不可通行,计数为1或者大于1时,计数减1且允许通过。

dispatch_semaphore 有三个函数,分别用来创建信号量,增加计数量

dispatch_semaphore_create:创建并初始化信号的总量
dispatch_semaphore_signal:发送一个信号,让信号总量加1
dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行

使用信号量时,需要清楚地分清等待和执行的线程。

应用:

1、保持线程同步,将异步执行的任务转为同步执行任务
2、保证线程安全,为线程加锁

应用一:异步转同步

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"1:%@",[NSThread currentThread]);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore end");

结果

我们在当前线程中开启了一个异步并行队列任务,由于我们使用了信号量semaphore(其初始值为0,不可通行),当我们使用dispatch_semaphore_wait,会阻塞当前线程,直到信号量不为0时,才会执行NSLog(@"semaphore end")的输出,上述例子中,并没有给信号量计数器加1,因此不会执行后面输出任务。

我们为其添加加一

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"1:%@",[NSThread currentThread]);
    dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore end");

结果

此时我们发现,程序正常输出。另外,我们这里通过通过信号量实现线程的同步操作(输出在异步任务之后),原本的异步线程在这里并没有什么效果,和同步任务没有任何区别

应用二:线程安全

结合GCD的信号量的特性,我们还可以使用其达到线程安全的目的,即在多线程下,保证事务的原子性。

假设我分别开启两个线程去做加一操作,在不保证线程安全的情况下,势必会出现线程争抢资源,导致意想不到的错误产生。

首先我们来看下非线程安全。

dispatch_queue_t queue1 = dispatch_queue_create("one", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("two", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue1, ^{
    [self addCount];
});
dispatch_async(queue2, ^{
    [self addCount];
});

-(void)addCount{
    while (1) {
        if (count>=100) {
            break;
        }else{
            count++;
            NSLog(@"%ld-%@",count,[NSThread currentThread]);
        }
    }
}

结果

我们发现,在此次过程中,线程3和4发生了资源争抢的问题,导致在加1的过程中发生了错误,两个线程累加的次数总和超过了100次。

接下来,我们使用信号量来保证事务的原子性。

// 创建信号量
semaphore = dispatch_semaphore_create(1);
// 在事务的开始和结束时操作信号量
-(void)addCount{
    while (1) {
        // 信号量减一,进入等待状态
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        if (count>=100) {
            // 信号量加一,进入可通行状态
            dispatch_semaphore_signal(semaphore);
            break;
        }else{
            count++;
            NSLog(@"%ld-%@",count,[NSThread currentThread]);
        }
        // 信号量加一,进入可通行状态
        dispatch_semaphore_signal(semaphore);
    }
}

结果

我们发现,线程3和4交替操作累加,数字累计达到100时,操作总和为100,因此我们可以认定,信号量起到了很好的效果,保证了事务的原子性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值