GCD

GCD,全名Grand Central Dispatch,中文名郭草地,是基于C语言的一套多线程开发API,一听名字就是个狠角色,也是目前苹果官方推荐的多线程开发方式。可以说是使用方便,又不失逼格。总体来说,他解决我提到的上面直接操作线程带来的难题,它自动帮你管理了线程的生命周期以及任务的执行规则。下面我们会频繁的说道一个词,那就是任务,说白了,任务其实就是你要执行的那段代码。

任务管理方式——队列

上面说当我们要管理多个任务时,线程开发给我们带来了一定的技术难度,或者说不方便性,GCD给出了我们统一管理任务的方式,那就是队列。我们来看一下iOS多线程操作中的队列:(??不管是串行还是并行,队列都是按照FIFO的原则依次触发任务)

两个通用队列:

  • 串行队列:所有任务会在一条线程中执行(有可能是当前线程也有可能是新开辟的线程),并且一个任务执行完毕后,才开始执行下一个任务。(等待完成)

  • 并行队列:可以开启多条线程并行执行任务(但不一定会开启新的线程),并且当一个任务放到指定线程开始执行时,下一个任务就可以开始执行了。(等待发生)

两个特殊队列:

  • 主队列:系统为我们创建好的一个串行队列,牛逼之处在于它管理必须在主线程中执行的任务,属于有劳保的。

  • 全局队列:系统为我们创建好的一个并行队列,使用起来与我们自己创建的并行队列无本质差别。

任务执行方式

说完队列,相应的,任务除了管理,还得执行,要不然有钱不花,掉了白搭,并且在GCD中并不能直接开辟线程执行任务,所以在任务加入队列之后,GCD给出了两种执行方式——同步执行(sync)和异步执行(async)。

  • 同步执行:在当前线程执行任务,不会开辟新的线程。必须等到Block函数执行完毕后,dispatch函数才会返回。

  • 异步执行:可以在新的线程中执行任务,但不一定会开辟新的线程。dispatch函数会立即返回, 然后Block在后台异步执行。

上面的这些理论都是本人在无数被套路背后总结出来的血淋淋的经验,与君共享,但是这么写我猜你一定还是不明白,往下看,说不定有惊喜呢

任务队列组合方式

相信这个标题你看过无数次?是不是看完也不知道到底怎么用?这么巧,我也是,请相信下面这些肯定有你不知道并且想要的,我们从两个最直接的点切入:

1. 线程死锁

这个你是不是也看过无数次?哈哈哈!你是不是觉得我又要开始复制黏贴了?请往下看:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

运行结果:

打印结果:

1
ThreadDemo[5615:874679] 1========{number = 1, name = main}

真不是我套路你,我们还是得分析一下为什么会死锁,因为总得为那些没有饱受过套路的人心里留下一段美好的回忆,分享代码,我们是认真的!

事情是这样的:

我们先做一个定义:- (void)viewDidLoad{} ---> 任务A,GCD同步函数 --->任务B。

总而言之呢,大概是这样的,首先,任务A在主队列,并且已经开始执行,在主线程打印出1===... ...,然后这时任务B被加入到主队列中,并且同步执行,这尼玛事都大了,系统说,同步执行啊,那我不开新的线程了,任务B说我要等我里面的Block函数执行完成,要不我就不返回,但是主队列说了,玩蛋去,我是串行的,你得等A执行完才能轮到你,不能坏了规矩,同时,任务B作为任务A的内部函数,必须等任务B执行完函数返回才能执行下一个任务。那就造成了,任务A等待任务B完成才能继续执行,但作为串行队列的主队列又不能让任务B在任务A未完成之前开始执行,所以任务A等着任务B完成,任务B等着任务A完成,等待,永久的等待。所以就死锁了。简单不?下面我们慎重看一下我们无意识书写的代码!

2. 这样不死锁

不如就写个最简单的:

1
2
3
4
5
6
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    NSLog(@"2========%@",[NSThread currentThread]);
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印结果:

1
2
3
ThreadDemo[5803:939324] 1========{number = 1, name = main}
ThreadDemo[5803:939324] 2========{number = 1, name = main}
ThreadDemo[5803:939324] 3========{number = 1, name = main}

之前有人问:顺序打印,没毛病,全在主线程执行,而且顺序执行,那它们一定是在主队列同步执行的啊!那为什么没有死锁?苹果的操作系统果然高深啊!

其实这里有一个误区,那就是任务在主线程顺序执行就是主队列。其实一点关系都没有,如果当前在主线程,同步执行任务,不管在什么队列任务都是顺序执行。把所有任务都以异步执行的方式加入到主队列中,你会发现它们也是顺序执行的。而且任务的执行不一定非得撕扯白咧的加入到队列中才可以啊!

相信你知道上面的死锁情况后,你一定会手贱改成这样试试:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印结果:

1
2
3
ThreadDemo[5830:947858] 1========{number = 1, name = main}
ThreadDemo[5830:947858] 2========{number = 1, name = main}
ThreadDemo[5830:947858] 3========{number = 1, name = main}

你发现正常执行了,并且是顺序执行的,你是不是若有所思,没错,你想的和我想的是一样的,和上诉情况一样,任务A在主队列中,但是任务B加入到了全局队列,这时候,任务A和任务B没有队列的约束,所以任务B就先执行喽,执行完毕之后函数返回,任务A接着执行。

我猜你一定手贱这么改过:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印结果:

1
2
3
ThreadDemo[5911:962470] 1========{number = 1, name = main}
ThreadDemo[5911:962470] 3========{number = 1, name = main}
ThreadDemo[5911:962470] 2========{number = 1, name = main}

细心而帅气的你一定发现不是顺序打印了,而且也不会死锁,明明都是加到主队列里了啊,其实当任务A在执行时,任务B加入到了主队列,注意哦,是异步执行,所以dispatch函数不会等到Block执行完成才返回,dispatch函数返回后,那任务A可以继续执行,Block任务我们可以认为在下一帧顺序加入队列,并且默认无限下一帧执行。这就是为什么你看到2===... ...是最后输出的了。(??一个函数的有多个内部函数异步执行时,不会造成死锁的同时,任务A执行完毕后,这些异步执行的内部函数会顺序执行)。

我们说说队列与执行方式的搭配

上面说了系统自带的两个队列,下面我们来用自己创建的队列研究一下各种搭配情况。

我们先创建两个队列,并且测试方法都是在主线程中调用:

1
2
3
4
//串行队列
self.serialQueue = dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL);
//并行队列
self.concurrentQueue = dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT);

1. 串行队列 + 同步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)queue_taskTest{
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印结果:

1
2
3
4
ThreadDemo[6735:1064390] 1========{number = 1, name = main}
ThreadDemo[6735:1064390] 2========{number = 1, name = main}
ThreadDemo[6735:1064390] 3========{number = 1, name = main}
ThreadDemo[6735:1064390] 4========{number = 1, name = main}

全部都在当前线程顺序执行,也就是说,同步执行不具备开辟新线程的能力。

2. 串行队列 + 异步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)queue_taskTest{
    dispatch_async(self.serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印结果:

1
2
3
4
ThreadDemo[6774:1073235] 4========{number = 1, name = main}
ThreadDemo[6774:1073290] 1========{number = 3, name = (null)}
ThreadDemo[6774:1073290] 2========{number = 3, name = (null)}
ThreadDemo[6774:1073290] 3========{number = 3, name = (null)}

先打印了4,然后顺序在子线程中打印1,2,3。说明异步执行具有开辟新线程的能力,并且串行队列必须等到前一个任务执行完才能开始执行下一个任务,同时,异步执行会使内部函数率先返回,不会与正在执行的外部函数发生死锁。

3. 并行队列 + 同步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)queue_taskTest{
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

运行结果:

1
2
3
4
ThreadDemo[7012:1113594] 1========{number = 1, name = main}
ThreadDemo[7012:1113594] 2========{number = 1, name = main}
ThreadDemo[7012:1113594] 3========{number = 1, name = main}
ThreadDemo[7012:1113594] 4========{number = 1, name = main}

未开启新的线程执行任务,并且Block函数执行完成后dispatch函数才会返回,才能继续向下执行,所以我们看到的结果是顺序打印的。

4. 并行队列 + 异步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)queue_taskTest{
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印结果:

1
2
3
4
ThreadDemo[7042:1117492] 1========{number = 3, name = (null)}
ThreadDemo[7042:1117491] 3========{number = 5, name = (null)}
ThreadDemo[7042:1117451] 4========{number = 1, name = main}
ThreadDemo[7042:1117494] 2========{number = 4, name = (null)}

开辟了多个线程,触发任务的时机是顺序的,但是我们看到完成任务的时间却是随机的,这取决于CPU对于不同线程的调度分配,但是,线程不是无条件无限开辟的,当任务量足够大时,线程是会重复利用的。

划一下重点啊

1. 对于单核CPU来说,不存在真正意义上的并行,所以,多线程执行任务,其实也只是一个人在干活,CPU的调度决定了非等待任务的执行速率,同时对于非等待任务,多线程并没有真正意义提高效率。

2. 线程可以简单的认为就是一段代码+运行时数据。

3. 同步执行会在当前线程执行任务,不具备开辟线程的能力或者说没有必要开辟新的线程。并且,同步执行必须等到Block函数执行完毕,dispatch函数才会返回,从而阻塞同一串行队列中外部方法的执行。

4. 异步执行dispatch函数会直接返回,Block函数我们可以认为它会在下一帧加入队列,并根据所在队列目前的任务情况无限下一帧执行,从而不会阻塞当前外部任务的执行。同时,只有异步执行才有开辟新线程的必要,但是异步执行不一定会开辟新线程。

5. 只要是队列,肯定是FIFO(先进先出),但是谁先执行完要看第1条。

6. 只要是串行队列,肯定要等上一个任务执行完成,才能开始下一个任务。但是并行队列当上一个任务开始执行后,下一个任务就可以开始执行。

7. 想要开辟新线程必须让任务在异步执行,想要开辟多个线程,只有让任务在并行队列中异步执行才可以。执行方式和队列类型多层组合在一定程度上能够实现对于代码执行顺序的调度。

8. 同步+串行:未开辟新线程,串行执行任务;同步+并行:未开辟新线程,串行执行任务;异步+串行:新开辟一条线程,串行执行任务;异步+并行:开辟多条新线程,并行执行任务;在主线程中同步使用主队列执行任务,会造成死锁。

8. 对于多核CPU来说,线程数量也不能无限开辟,线程的开辟同样会消耗资源,过多线程同时处理任务并不是你想像中的人多力量大。

GCD其他函数用法

1. dispatch_after

该函数用于任务延时执行,其中参数dispatch_time_t代表延时时长,dispatch_queue_t代表使用哪个队列。如果队列未主队列,那么任务在主线程执行,如果队列为全局队列或者自己创建的队列,那么任务在子线程执行,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(void)GCDDelay{
    //主队列延时
    dispatch_time_t when_main = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(when_main, dispatch_get_main_queue(), ^{
        NSLog(@"main_%@",[NSThread currentThread]);
    });
    //全局队列延时
    dispatch_time_t when_global = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));
    dispatch_after(when_global, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"global_%@",[NSThread currentThread]);
    });
    //自定义队列延时
    dispatch_time_t when_custom = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    dispatch_after(when_custom, self.serialQueue, ^{
        NSLog(@"custom_%@",[NSThread currentThread]);
    });
}

打印结果:

1
2
3
ThreadDemo[1508:499647] main_{number = 1, name = main}
ThreadDemo[1508:499697] global_{number = 3, name = (null)}
ThreadDemo[1508:499697] custom_{number = 3, name = (null)}

2. dispatch_once

保证函数在整个生命周期内只会执行一次,看代码。

1
2
3
4
5
6
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

打印结果:

1
ThreadDemo[1524:509261] {number = 1, name = main}
1
无论你怎么疯狂的点击,在第一次打印之后,输出台便岿然不动。

3. dispatch_group_async & dispatch_group_notify

试想,现在牛逼的你要现在两张小图,并且你要等两张图都下载完成之后把他们拼起来,你要怎么做?我根本就不会把两张图拼成一张图啊,牛逼的我怎么可能有这种想法呢?

其实方法有很多,比如你可以一张一张下载,再比如使用局部变量和Blcok实现计数,但是既然今天我们讲到这,那我们就得入乡随俗,用GCD来实现,有一个神器的东西叫做队列组,当加入到队列组中的所有任务执行完成之后,会调用dispatch_group_notify函数通知任务全部完成,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-(void)GCDGroup{
    //
    [self jointImageView];
    //
    dispatch_group_t group = dispatch_group_create();
    __block UIImage *image_1 = nil;
    __block UIImage *image_2 = nil;
    //在group中添加一个任务
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image_2 = [self imageWithPath:@"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=776127947,2002573948&fm=26&gp=0.jpg"];
    });
    //group中所有任务执行完毕,通知该方法执行
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        self.imageView_1.image = image_1;
        self.imageView_2.image = image_2;
        //
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0f);
        [image_2 drawInRect:CGRectMake(0, 0, 100, 100)];
        [image_1 drawInRect:CGRectMake(100, 0, 100, 100)];
        UIImage *image_3 = UIGraphicsGetImageFromCurrentImageContext();
        self.imageView_3.image = image_3;
        UIGraphicsEndImageContext();
    });
}
-(void)jointImageView{
    self.imageView_1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 50, 100, 100)];
    [self.view addSubview:_imageView_1];
     
    self.imageView_2 = [[UIImageView alloc] initWithFrame:CGRectMake(140, 50, 100, 100)];
    [self.view addSubview:_imageView_2];
     
    self.imageView_3 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 200, 200, 100)];
    [self.view addSubview:_imageView_3];
     
    self.imageView_1.layer.borderColor = self.imageView_2.layer.borderColor = self.imageView_3.layer.borderColor = [UIColor grayColor].CGColor;
    self.imageView_1.layer.borderWidth = self.imageView_2.layer.borderWidth = self.imageView_3.layer.borderWidth = 1;
}

4. dispatch_barrier_async

栅栏函数,这么看来它能挡住或者分隔什么东西,别瞎猜了,反正你又猜不对,看这,使用此方法创建的任务,会查找当前队列中有没有其他任务要执行,如果有,则等待已有任务执行完毕后再执行,同时,在此任务之后进入队列的任务,需要等待此任务执行完成后,才能执行。看代码,老铁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-(void)GCDbarrier{
     
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });
     
//    dispatch_barrier_async(self.concurrentQueue, ^{
//        NSLog(@"任务barrier");
//    });
     
//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

运行结果:

1
2
3
4
ThreadDemo[1816:673351] 任务3
ThreadDemo[1816:673353] 任务1
ThreadDemo[1816:673350] 任务2
ThreadDemo[1816:673370] 任务4

是不是如你所料,牛逼大了,下面我们打开第一句注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-(void)GCDbarrier{
     
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });
     
    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任务barrier");
    });
     
//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

打印结果:

1
2
3
4
5
ThreadDemo[1833:678739] 任务2
ThreadDemo[1833:678740] 任务1
ThreadDemo[1833:678740] 任务barrier
ThreadDemo[1833:678740] 任务3
ThreadDemo[1833:678739] 任务4

这个结果和我们上面的解释完美契合,我们可以简单的控制函数执行的顺序了,你离大牛又近了一步,如果现在的你不会怀疑还有dispatch_barrier_sync这个函数的话,说明... ...嘿嘿嘿,我们看一下这个函数和上面我们用到的函数的区别,你一定想到了,再打开第二个和第三个注释,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-(void)GCDbarrier{        
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });
     
    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任务barrier");
    });
     
    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

运行结果:

1
2
3
4
5
6
7
ThreadDemo[1853:692434] 任务1
ThreadDemo[1853:692421] 任务2
ThreadDemo[1853:692387] big
ThreadDemo[1853:692421] 任务barrier
ThreadDemo[1853:692387] apple
ThreadDemo[1853:692421] 任务3
ThreadDemo[1853:692434] 任务4

不要着急,我们换一下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-(void)GCDbarrier{
     
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });
     
    dispatch_barrier_sync(self.concurrentQueue, ^{
        NSLog(@"任务barrier");
    });
     
    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

打印结果:

1
2
3
4
5
6
7
ThreadDemo[1874:711841] 任务1
ThreadDemo[1874:711828] 任务2
ThreadDemo[1874:711793] 任务barrier
ThreadDemo[1874:711793] big
ThreadDemo[1874:711793] apple
ThreadDemo[1874:711828] 任务3
ThreadDemo[1874:711841] 任务4

老铁,发现了吗?这两个函数对于队列的栅栏作用是一样的,但是对于该函数相对于其他内部函数遵循了最开始说到的同步和异步的规则。你是不是有点懵逼,如果你蒙蔽了,那么请在每一个输出后面打印出当前的线程,如果你还是懵逼,那么请你重新看,有劳,不谢!

5. dispatch_apply

该函数用于重复执行某个任务,如果任务队列是并行队列,重复执行的任务会并发执行,如果任务队列为串行队列,则任务会顺序执行,需要注意的是,该函数为同步函数,要防止线程阻塞和死锁哦,老铁。

串行队列:

1
2
3
4
5
6
-(void)GCDApply{
    //重复执行
    dispatch_apply(5, self.serialQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

运行结果:

1
2
3
4
5
ThreadDemo[1446:158101] 第0次_{number = 1, name = main}
ThreadDemo[1446:158101] 第1次_{number = 1, name = main}
ThreadDemo[1446:158101] 第2次_{number = 1, name = main}
ThreadDemo[1446:158101] 第3次_{number = 1, name = main}
ThreadDemo[1446:158101] 第4次_{number = 1, name = main}

并行队列:

1
2
3
4
5
6
-(void)GCDApply{
    //重复执行
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

运行结果:

1
2
3
4
5
ThreadDemo[1461:160567] 第2次_{number = 4, name = (null)}
ThreadDemo[1461:160534] 第0次_{number = 1, name = main}
ThreadDemo[1461:160566] 第3次_{number = 5, name = (null)}
ThreadDemo[1461:160569] 第1次_{number = 3, name = (null)}
ThreadDemo[1461:160567] 第4次_{number = 4, name = (null)}

死锁:

1
2
3
4
5
6
-(void)GCDApply{
    //重复执行
    dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

运行结果:

6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

看这几个函数的时候你需要抛开队列,丢掉同步异步,不要把它们想到一起,混为一谈,信号量只是控制任务执行的一个条件而已,相对于上面通过队列以及执行方式来控制线程的开辟和任务的执行,它更贴近对于任务直接的控制。类似于单个队列的最大并发数的控制机制,提高并行效率的同时,也防止太多线程的开辟对CPU早层负面的效率负担。

dispatch_semaphore_create创建信号量,初始值不能小于0;

dispatch_semaphore_wait等待降低信号量,也就是信号量-1;

dispatch_semaphore_signal提高信号量,也就是信号量+1;

dispatch_semaphore_wait和dispatch_semaphore_signal通常配对使用。

看一下代码吧,老铁。

1
2
3
4
5
6
7
8
9
10
11
-(void)GCDSemaphore{
    //
    //dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        //dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            //dispatch_semaphore_signal(semaphore);
        });
    });
}

你能猜到运行结果吗?没错,就是你想的这样,开辟了5个线程执行任务。

1
2
3
4
5
ThreadDemo[1970:506692] 第0次_{number = 3, name = (null)}
ThreadDemo[1970:506711] 第1次_{number = 4, name = (null)}
ThreadDemo[1970:506713] 第2次_{number = 5, name = (null)}
ThreadDemo[1970:506691] 第3次_{number = 6, name = (null)}
ThreadDemo[1970:506694] 第4次_{number = 7, name = (null)}

下一步你一定猜到了,把注释的代码打开:

1
2
3
4
5
6
7
8
9
10
11
-(void)GCDSemaphore{
    //
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
    });
}

运行结果:

1
2
3
4
5
ThreadDemo[2020:513651] 第0次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第1次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第2次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第3次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第4次_{number = 3, name = (null)}

很明显,我开始说的是对的,哈哈哈哈,信号量是控制任务执行的重要条件,当信号量为0时,所有任务等待,信号量越大,允许可并行执行的任务数量越多。

GCD就先说到这,很多API没有涉及到,有兴趣的同学们可以自己去看看,重要的是方法和习惯,而不是你看过多少

转自:http://www.cocoachina.com/ios/20170829/20404.html

GCD那些事儿的更多相关文章

  1. GCD: 基本概念和Dispatch Queue 【转】

    什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像 NSOperationQueue,他们都允 ...

  2. GCD详解

    什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像 NSOperationQueue,他们都允 ...

  3. Block编程值得注意的那些事儿

    [深入浅出Cocoa]Block编程值得注意的那些事儿   [深入浅出Cocoa]Block编程值得注意的那些事儿 罗朝辉 (http://www.cnblogs.com/kesalin/) 本文遵循 ...

  4. ios专题 - GCD(2)

    何为Dispatch Sources 简单来说,dispatch source是一个监视某些类型事件的对象.当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中. ...

  5. ios专题 - GCD(1)

    什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像 NSOperationQueue,他们都允 ...

  6. GCD介绍(一): 基本概念和Dispatch Queue

    什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像NSOperationQueue,他们都允许 ...

  7. GCD介绍(三): Dispatch Sources

    何为Dispatch Sources         简单来说,dispatch source是一个监视某些类型事件的对象.当这些事件发生时,它自动将一个block放入一个dispatch queue ...

  8. GCD系列 之(一):基本概念和Dispatch Queue

    参考学习https://www.dreamingwish.com/article/grand-central-dispatch-basic-1.html系列文章,貌似也是翻译自他处的.觉得非常完整,就 ...

  9. GCD教程(三):Dispatch Sources

    接上一篇,原帖地址:http://www.dreamingwish.com/dream-2012/intro-to-grand-central-dispatch-part-iii-the-dispat ...

随机推荐

  1. TP5.1:实现分页

    前提: (1)为了让分页变得更加好看,我的案例加载了bootstrap和jq的文件,具体操作请参考:http://www.cnblogs.com/finalanddistance/p/9033916. ...

  2. 从windows CMD 命令行(CMD promp)运行Docker

    英文原帖 Running Docker from Windows CMD prompt https://medium.com/@neil.avery_68603/running-docker-from ...

  3. Bonita portal 源码编译(未完成)

    首先下载源代码 https://github.com/bonitasoft/bonita-portal-js 以下内容为Github 的安装教程包含我安装过程中遇到的问题.并加以修正 Bonita p ...

  4. sublim插件(待续)

    imesupport SublimeText3默认不支持输入法跟随光标,这在输入中文的时候看起来不方便. 进入SublimeText3在上面菜单栏里Perferences点击PackageContro ...

  5. java研发常见问题总结2

    1. String.StringBuffer与StringBuilder之间区别 关于这三个类在字符串处理中的位置不言而喻,那么他们到底有什么优缺点,到底什么时候该用谁呢?下面我们从以下几点说明一下 ...

  6. Ubuntu 如何将桌面上的Home中的文件夹除去

    安装Ubuntu后, 由于无法用Terminal(终端)进入带中文的文件夹,会引起很多操作不便.很多朋友想到了将它们都改成中文,但是当再次开机重启使却会发现,原本光洁的桌面现在竟然出现了一堆文件夹?? ...

  7. 4. NBU文件备份与恢复,图形界面&字符界面操作

    一. 图形界面文件备份与恢复 1.1  文件备份 待补充 1.2 Windows文件恢复 (1) 打开恢复客户端 (2) 检查设置 (3) 查询可恢复信息 (4) 选取恢复时间点和文件 (5) 选择恢 ...

  8. Linux中的/etc/nologin问题

    /etc/nologin 文件给系统管理员提供了在 Linux 系统维护期间禁止用户登陆的方式. 如果系统中存在 /etc/nologin 文件那么普通用户登陆就会失败. 这是一种提高安全性和防止数据 ...

  9. 10分钟了解 代理模式与java中的动态代理

    前言    代理模式又分为静态代理与动态代理,其中动态代理是Java各大框架中运用的最为广泛的一种模式之一,下面就用简单的例子来说明静态代理与动态代理. 场景    李雷是一个唱片公司的大老板,很忙, ...

  10. python第一章练习题

    本章总节 练习题 1.简述编译型与解释型语言的区别,且分别列出你知道的哪些语言属于编译型,哪些属于解释 编译型:把源代码编译成机器语言的可执行文件,程序执行的时候执行可执行文件即可. 优点:程序执行不 ...