dispatch_group & semaphore

GCD

Posted by MAYBESHEWILL on May 10, 2017

dispatch_group从名字上也知道他是一个组,他的用法就是把一组任务提交到队列中,然后可以监听这些任务. 常见的方法:

  1. dispatch_group_create创建一个调度任务组
  2. dispatch_group_async 把一个任务异步提交到任务组里
  3. dispatch_group_enter/dispatch_group_leave 这种方式用在不使用dispatch_group_async来提交任务,且必须配合使用
  4. dispatch_group_notify 用来监听任务组事件的执行完毕
  5. dispatch_group_wait 设置等待时间,在等待时间结束后,如果还没有执行完任务组,则返回。返回0代表执行成功,非0则执行失败.稍后会提到semaphore的问题

实际的使用场景
现在有4个任务: 任务1、任务2、任务3、任务4. 且任务3必须在任务2之后,任务4必须在前3个任务都执行完成后,才能执行,并且需要在主线程更新UI。也就是说要控制他们的顺序.

那么也就是:

任务3必须在任务2之后,所以这两个必须串行执行,同时,任务2和3整体可以和任务1并行执行,最后,任务4只能等待前3个任务全部执行完成,才能执行。

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
-(void)disGroup{
dispatch_queue_t globalQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t selfQuene = dispatch_queue_create("myQuene", 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, globalQuene, ^{
    NSLog(@"run task 1");
});
dispatch_group_async(group, selfQuene, ^{
    NSLog(@"run task 2");
});
dispatch_group_async(group, selfQuene, ^{
    NSLog(@"run task 3");
});

// 这个notify是等到 Group 中都执行完了才会执行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"run task 4");
    });
}


2019-01-16 09:56:06.990423+0800 GCD[438:139258] run task 1
2019-01-16 09:56:06.990509+0800 GCD[438:139260] run task 2
2019-01-16 09:56:06.990523+0800 GCD[438:139260] run task 3
2019-01-16 09:56:07.013527+0800 GCD[438:139229] run task 4

总结: 1和(2、3)是并行执行关系,2、3是串行执行关系,且3肯定在2之后,而4在(1、2、3)全部完成之后才会执行。

dispatch_group_enter(group)、dispatch_group_leave(group)这个用的很少… 这两个方法其实就是:手动管理group关联的block的运行状态(或计数),并且使用时必须保证进入和退出group次数匹配。 AB 其实一样的.

1
2
3
4
5
6
7
8
9
10
11
A)
dispatch_group_async(group, queue, ^{
	// 。。。
});
 
B)
dispatch_group_enter(group);
dispatch_async(queue, ^{
	//。。。
  dispatch_group_leave(group);
});

例子:有3个异步请求任务,任务1、2、3,在3个任务全部完成之后,需要执行任务4,用以显示界面数据。

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
dispatch_queue_t globalQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
 
//任务1
dispatch_group_enter(group);
dispatch_async(globalQuene, ^{
     NSLog(@"run task 1");
    sleep(1);
    dispatch_group_leave(group);
});
 
//任务2
dispatch_group_enter(group);
dispatch_async(globalQuene, ^{
    NSLog(@"run task 2");
    sleep(2);
    dispatch_group_leave(group);
});
 
//任务3
dispatch_group_enter(group);
dispatch_async(globalQuene, ^{
    NSLog(@"run task 3");
    sleep(3);
    dispatch_group_leave(group);
});
 
//一直等待完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
   
//任务3
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"run task 4");
});

注意看: 这三个任务是异步执行的. 即使顺序可能不同,(当然也可以用串行队列)但可以肯定的是一共用了3秒时间.之后执行4

1
2
3
4
2019-01-16 10:00:42.126815+0800 GCD[443:140782] run task 1
2019-01-16 10:00:42.126848+0800 GCD[443:140780] run task 2
2019-01-16 10:00:42.126864+0800 GCD[443:140783] run task 3
2019-01-16 10:00:45.182753+0800 GCD[443:140756] run task 4

dispatch_semaphore

先来看看dispatch_semaphore 能解决什么问题

假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理

我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。

定义:

信号量:就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。其实类似于锁的机制,不过信号量都是系统处理.

信号量主要的三个函数:

1
2
3
4
5
6
7
8
//创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_create(信号量值)
 
//等待降低信号量
dispatch_semaphore_wait(信号量,等待时间)
 
//提高信号量
dispatch_semaphore_signal(信号量) 注意,正常的使用顺序是先降低然后再提高,这两个函数通常成对使用。

我们试着来解决第一个问题:

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
-(void)dispatchSignal{
//crate的value表示,最多几个资源可访问
	dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);   
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
//任务1
	dispatch_async(quene, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    	NSLog(@"run task 1");
    	sleep(1);
    	NSLog(@"complete task 1");
    	dispatch_semaphore_signal(semaphore);       
});
//任务2
dispatch_async(quene, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    	NSLog(@"run task 2");
    	sleep(1);
    	NSLog(@"complete task 2");
    	dispatch_semaphore_signal(semaphore);       
});
//任务3
	dispatch_async(quene, ^{
    	dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    	NSLog(@"run task 3");
    	sleep(1);
    	NSLog(@"complete task 3");
    	dispatch_semaphore_signal(semaphore);       
});   
} 

	
2019-01-16 10:01:40.590603+0800 GCD[445:141270] run task 1
2019-01-16 10:01:40.590659+0800 GCD[445:141277] run task 2
2019-01-16 10:01:41.595787+0800 GCD[445:141270] complete task 1
2019-01-16 10:01:41.595787+0800 GCD[445:141277] complete task 2
2019-01-16 10:01:41.596065+0800 GCD[445:141269] run task 3
2019-01-16 10:01:42.601226+0800 GCD[445:141269] complete task 3

来分析一下,异步执行所以顺序可能不同.task1 先 wait 一次,即信号量-1,此时为1,打印run task1而 task2 此时也在执行,也wait一次,即-1,此时为0.打印run task2 .此时进入等待状态,所以task3并不能执行.而是等到 task1,或者 task2 完成之后,(complete task1 or conplete task2)执行signal即发送信号.此时进行+1操作,此时总信号量已不为0可以继续执行. task3 这时候才能开始执行.
如果初始值 creat 1 呢?

1
2
3
4
5
6
2019-01-16 10:02:29.081797+0800 GCD[447:141720] run task 1
2019-01-16 10:02:30.087030+0800 GCD[447:141720] complete task 1
2019-01-16 10:02:30.087359+0800 GCD[447:141718] run task 2
2019-01-16 10:02:31.092535+0800 GCD[447:141718] complete task 2
2019-01-16 10:02:31.092747+0800 GCD[447:141719] run task 3
2019-01-16 10:02:32.097998+0800 GCD[447:141719] complete task 3

可以看得出,由于信号量的限制,总是信号量大于0时才能继续. 那如果初始值 creat 3 呢? 噗…不用看也知道了,最多才开三个线程,也就相当于没有进行信号量的限制.

1
2
3
4
5
6
2019-01-16 10:03:05.737471+0800 GCD[450:142158] run task 1
2019-01-16 10:03:05.737522+0800 GCD[450:142157] run task 3
2019-01-16 10:03:05.737517+0800 GCD[450:142160] run task 2
2019-01-16 10:03:06.742733+0800 GCD[450:142157] complete task 3
2019-01-16 10:03:06.742733+0800 GCD[450:142160] complete task 2
2019-01-16 10:03:06.742733+0800 GCD[450:142158] complete task 1
文章使用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议