0%

iOS 信号量使用篇

信号量使用篇

操作GCD信号量的函数

在iOS中有三个函数可以操作GCD信号量:

dispatch_semaphore_t dispatch_semaphore_create(long value); 创建一个信号量。

long dispatch_semaphore_signal(dispatch_semaphore_t dsema); 发送一个信号.

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); 等待一个信号。

函数说明

  1. dispatch_semaphore_t semaphoreTask1 = dispatch_semaphore_create(0);
    创建一个信号量,并赋予初始值(必须传一个>=0的值)。eg:传入2则表示同时最多两个线程可以访问临界区。

  2. long signalRs = dispatch_semaphore_signal(semaphoreTask1);
    发出一个信号,将信号量加1。如果之前的信号量小于0,说明有等待dispatch semaphore的计数值增加的线程,那么该函数在返回前将唤醒最先处于等待状态的线程。

    返回值:This function returns non-zero if a thread is woken. Otherwise, zero is returned。如果有线程被唤醒则返回一个非0值,否则返回0。

  3. long waitRs = dispatch_semaphore_wait(semaphoreTask1, DISPATCH_TIME_FOREVER);
    将信号量-1。减去1后如果得到的值<0,则该函数在返回前将一直等待信号的发出,线程会被阻塞。减去1后如果得到的值>=0,则正常返回0,不阻塞线程。

    timeout:指定等待的时间。如果线程要被阻塞该值表明将阻塞该线程多久。传DISPATCH_TIME_FOREVER意味着该线程永久等待一个信号的发出。 传一个其他值如:dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),表明将阻塞该线程2s,2s后函数将返回,线程继续执行(不一定就是执行临界区的代码,可以判断如果返回的是非0表明是等待超时了可以做其他处理)。

    返回值:Returns zero on success, or non-zero if the timeout occurred。

eg:使用信号量控制多线程添加数组元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)testSemaphoreLock {
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *arr = [[NSMutableArray alloc] init];
self.semaphoreLock = dispatch_semaphore_create(1);
for (int i = 0; i < 10; i++) {
dispatch_async(globalQueue, ^{
NSLog(@"线程:%@ 执行 i = %d", [NSThread currentThread], i);
long waitRs = dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
NSLog(@"线程:%@,dispatch_semaphore_wait:%ld", [NSThread currentThread], waitRs);
NSNumber *number = [NSNumber numberWithInt:i];
NSLog(@"线程:%@,将要添加:%@", [NSThread currentThread], number);
[arr addObject:number];
long signalRs = dispatch_semaphore_signal(self.semaphoreLock);
NSLog(@"线程:%@,dispatch_semaphore_signal:%ld", [NSThread currentThread], signalRs);
});
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"count:%ld, %@",arr.count, arr);
});
}

打印:

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
42
43
44
45
46
47
48
49
50
51
52
2020-06-04 22:26:02.043402+0800 multithreadDemo[71599:7993600] 线程:<NSThread: 0x604000468580>{number = 3, name = (null)} 执行  i = 1
2020-06-04 22:26:02.043669+0800 multithreadDemo[71599:7993602] 线程:<NSThread: 0x60400046df80>{number = 4, name = (null)} 执行 i = 0
2020-06-04 22:26:02.043674+0800 multithreadDemo[71599:7993599] 线程:<NSThread: 0x604000861180>{number = 5, name = (null)} 执行 i = 2
2020-06-04 22:26:02.043764+0800 multithreadDemo[71599:7993601] 线程:<NSThread: 0x6040008688c0>{number = 6, name = (null)} 执行 i = 3
2020-06-04 22:26:02.043824+0800 multithreadDemo[71599:7993710] 线程:<NSThread: 0x600000476f80>{number = 7, name = (null)} 执行 i = 4
2020-06-04 22:26:02.043892+0800 multithreadDemo[71599:7993711] 线程:<NSThread: 0x6000004771c0>{number = 9, name = (null)} 执行 i = 5
2020-06-04 22:26:02.043892+0800 multithreadDemo[71599:7993712] 线程:<NSThread: 0x60400086a3c0>{number = 8, name = (null)} 执行 i = 6
2020-06-04 22:26:02.044138+0800 multithreadDemo[71599:7993600] 线程:<NSThread: 0x604000468580>{number = 3, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.044284+0800 multithreadDemo[71599:7993713] 线程:<NSThread: 0x60400086aa00>{number = 10, name = (null)} 执行 i = 7
2020-06-04 22:26:02.044389+0800 multithreadDemo[71599:7993715] 线程:<NSThread: 0x60400086aa40>{number = 12, name = (null)} 执行 i = 9
2020-06-04 22:26:02.044352+0800 multithreadDemo[71599:7993714] 线程:<NSThread: 0x60400086aac0>{number = 11, name = (null)} 执行 i = 8
2020-06-04 22:26:02.045823+0800 multithreadDemo[71599:7993600] 线程:<NSThread: 0x604000468580>{number = 3, name = (null)},将要添加:1
2020-06-04 22:26:02.047405+0800 multithreadDemo[71599:7993600] 线程:<NSThread: 0x604000468580>{number = 3, name = (null)},dispatch_semaphore_signal:1
2020-06-04 22:26:02.047405+0800 multithreadDemo[71599:7993602] 线程:<NSThread: 0x60400046df80>{number = 4, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.049837+0800 multithreadDemo[71599:7993602] 线程:<NSThread: 0x60400046df80>{number = 4, name = (null)},将要添加:0
2020-06-04 22:26:02.050684+0800 multithreadDemo[71599:7993602] 线程:<NSThread: 0x60400046df80>{number = 4, name = (null)},dispatch_semaphore_signal:1
2020-06-04 22:26:02.050684+0800 multithreadDemo[71599:7993599] 线程:<NSThread: 0x604000861180>{number = 5, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.051183+0800 multithreadDemo[71599:7993599] 线程:<NSThread: 0x604000861180>{number = 5, name = (null)},将要添加:2
2020-06-04 22:26:02.055352+0800 multithreadDemo[71599:7993599] 线程:<NSThread: 0x604000861180>{number = 5, name = (null)},dispatch_semaphore_signal:1
2020-06-04 22:26:02.055352+0800 multithreadDemo[71599:7993601] 线程:<NSThread: 0x6040008688c0>{number = 6, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.055915+0800 multithreadDemo[71599:7993601] 线程:<NSThread: 0x6040008688c0>{number = 6, name = (null)},将要添加:3
2020-06-04 22:26:02.056057+0800 multithreadDemo[71599:7993601] 线程:<NSThread: 0x6040008688c0>{number = 6, name = (null)},dispatch_semaphore_signal:1
2020-06-04 22:26:02.056091+0800 multithreadDemo[71599:7993710] 线程:<NSThread: 0x600000476f80>{number = 7, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.056829+0800 multithreadDemo[71599:7993710] 线程:<NSThread: 0x600000476f80>{number = 7, name = (null)},将要添加:4
2020-06-04 22:26:02.057543+0800 multithreadDemo[71599:7993710] 线程:<NSThread: 0x600000476f80>{number = 7, name = (null)},dispatch_semaphore_signal:1
2020-06-04 22:26:02.057543+0800 multithreadDemo[71599:7993711] 线程:<NSThread: 0x6000004771c0>{number = 9, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.058272+0800 multithreadDemo[71599:7993711] 线程:<NSThread: 0x6000004771c0>{number = 9, name = (null)},将要添加:5
2020-06-04 22:26:02.058587+0800 multithreadDemo[71599:7993712] 线程:<NSThread: 0x60400086a3c0>{number = 8, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.058612+0800 multithreadDemo[71599:7993711] 线程:<NSThread: 0x6000004771c0>{number = 9, name = (null)},dispatch_semaphore_signal:1
2020-06-04 22:26:02.058969+0800 multithreadDemo[71599:7993712] 线程:<NSThread: 0x60400086a3c0>{number = 8, name = (null)},将要添加:6
2020-06-04 22:26:02.059605+0800 multithreadDemo[71599:7993712] 线程:<NSThread: 0x60400086a3c0>{number = 8, name = (null)},dispatch_semaphore_signal:1
2020-06-04 22:26:02.059675+0800 multithreadDemo[71599:7993713] 线程:<NSThread: 0x60400086aa00>{number = 10, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.060750+0800 multithreadDemo[71599:7993713] 线程:<NSThread: 0x60400086aa00>{number = 10, name = (null)},将要添加:7
2020-06-04 22:26:02.061001+0800 multithreadDemo[71599:7993713] 线程:<NSThread: 0x60400086aa00>{number = 10, name = (null)},dispatch_semaphore_signal:1
2020-06-04 22:26:02.061034+0800 multithreadDemo[71599:7993714] 线程:<NSThread: 0x60400086aac0>{number = 11, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.061310+0800 multithreadDemo[71599:7993714] 线程:<NSThread: 0x60400086aac0>{number = 11, name = (null)},将要添加:8
2020-06-04 22:26:02.061667+0800 multithreadDemo[71599:7993714] 线程:<NSThread: 0x60400086aac0>{number = 11, name = (null)},dispatch_semaphore_signal:1
2020-06-04 22:26:02.061686+0800 multithreadDemo[71599:7993715] 线程:<NSThread: 0x60400086aa40>{number = 12, name = (null)},dispatch_semaphore_wait:0
2020-06-04 22:26:02.062198+0800 multithreadDemo[71599:7993715] 线程:<NSThread: 0x60400086aa40>{number = 12, name = (null)},将要添加:9
2020-06-04 22:26:02.062506+0800 multithreadDemo[71599:7993715] 线程:<NSThread: 0x60400086aa40>{number = 12, name = (null)},dispatch_semaphore_signal:0
2020-06-04 22:26:07.043709+0800 multithreadDemo[71599:7993539] count:10, (
1,
0,
2,
3,
4,
5,
6,
7,
8,
9
)

从打印可以看到dispatch_semaphore_create(1)保证了同一时刻仅有一个线程可以访问临界区。不过上述数组中添加的元素顺序不是依次的。

应用场景

进行线程同步、控制线程的并发数量。

eg:控制网络请求的执行顺序。

Q:想让请求1完成之后,再进行网络请求2,然后进行网络请求N,要求不能阻塞主线程。

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
- (void)testSemaphoreLock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"currentThread:%@",[NSThread currentThread]);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

[self requestSomethingWithN:40 completion:^(NSInteger result) {
NSLog(@"40 rs = %ld", (long)result);
dispatch_semaphore_signal(semaphore);
}];

NSLog(@"===1");

//阻塞住子线程
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

NSLog(@"===2");

[self requestSomethingWithN:41 completion:^(NSInteger result) {
NSLog(@"41 rs = %ld", (long)result);
dispatch_semaphore_signal(semaphore);
}];

NSLog(@"===3");

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

NSLog(@"===4");

[self requestSomethingWithN:42 completion:^(NSInteger result) {
NSLog(@"42 rs = %ld", (long)result);
dispatch_semaphore_signal(semaphore);
}];
});
}

不过上面的情景也可以直接在请求完成block里再发起请求,信号量都不需要了。可以看一个稍微复杂点的:

Q:请求A和请求B同时发起,但需要二者都完成后再发起请求C。要求不能阻塞主线程,仅使用信号量完成。

这个如果使用dispatch_group_notify再配合dispatch_group_enter/leave就很简单,但如果仅使用信号量该怎么实现呢?

只需要在请求C发起前,wait两次阻塞住子线程,请求AB完成后发出信号即可。

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
- (void)testSemaphoreLock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"currentThread:%@",[NSThread currentThread]);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

__block NSInteger rs1 = 0;
[self requestSomethingWithN:40 completion:^(NSInteger result) {
rs1 = result;
NSLog(@"40 rs = %ld", (long)result);
dispatch_semaphore_signal(semaphore);
}];

__block NSInteger rs2 = 0;
[self requestSomethingWithN:41 completion:^(NSInteger result) {
rs2 = result;
NSLog(@"41 rs = %ld", (long)result);
dispatch_semaphore_signal(semaphore);
}];

NSLog(@"===1");

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

NSLog(@"===2");

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

NSLog(@"===3");
[self requestSomethingWithN:42 completion:^(NSInteger result) {
NSLog(@"42 rs = %ld", (long)result);
NSInteger total = rs1 + rs2 + result;
NSLog(@"total:%ld", (long)total);
}];
});
}

注意事项:

  • dispatch_semaphore_signal() 与 dispatch_semaphore_wait() 必须配对使用,否则在信号量销毁时容易导致EXC_BAD_INSTRUCTION崩溃。

释疑

1. 信号量会减为负数吗?会减为-2,-3…吗?

结论:会,如果很多线程调用wait的话,信号量的值会一直减。但对于wait超时返回的情况,系统会进行+1操作。

以前一直以为信号量减到0就不会再减了,其实不然。

通过po可以查看信号量的值:

1
2
(lldb) po self.semaphoreLock
<OS_dispatch_semaphore: semaphore[0x6000024960d0] = { xref = 4, ref = 1, port = 0x2403, value = -3, orig = 1 }>

可以设计一个测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
self.semaphoreLock = dispatch_semaphore_create(1); 
NSLog(@"---"); //这里po时,value = 1
dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
NSLog(@"---"); //这里po时,value = 0
for (int i = 1; i <= 3; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * i * NSEC_PER_SEC));
dispatch_semaphore_wait(self.semaphoreLock, time);
});
}

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---"); //这里po时,value = -3,因为上面减了三次。
});

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(12 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---"); //这里po时,由于上面三次wait超时,系统自动+1三次,所以最终value = 0
});

理解了信号的实现原理,上述代码就很容易理解。

2. dispatch_semaphore_signal() 与 dispatch_semaphore_wait() 不配对会导致什么问题?

从底层实现来看

1
2
3
4
5
6
7
8
9
10
11
signal时的判断
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}

销毁时的判断
if (dsema->dsema_value < dsema->dsema_orig) {
DISPATCH_CLIENT_CRASH(dsema->dsema_orig - dsema->dsema_value,
"Semaphore object deallocated while in use");
}

1.不能过度wait,导致计数值变成LONG_MIN,这个一般不会出现。

2.保证signal的次数要>=wait的次数,否则信号量在销毁时会产生崩溃。

为了避免上述崩溃,应该让signal和wait保持配对。这里多signal貌似也没啥问题。

3. 多线程调用dispatch_semaphore_signal/wait为啥不会导致线程安全问题?

因为dispatch_semaphore_signal/wait里面操作信号量计数值时是原子操作的。

1
2
long value = os_atomic_inc2o(dsema, dsema_value, release); //signal
long value = os_atomic_dec2o(dsema, dsema_value, acquire); //wait

4. 信号量变为-3 后,另一个线程发送信号还能唤醒一个等待的线程吗?

可以。dispatch_semaphore_signal的作用就是将信号量+1,如果加1后信号量还是<=0,说明有wait的线程,那就唤醒最先等待的线程。

5.信号量与互斥锁的区别

  1. 互斥锁的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
  2. 互斥量值只能为0/1,信号量值可以为非负整数。锁是服务于共享资源的;而semaphore是服务于多个线程间的执行的逻辑顺序的。semaphore的本质就是调度线程

一个最典型的使用semaphore的场景: a源自一个线程,b源自另一个线程,计算c = a + b也是一个线程。(即一共三个线程)

显然,第三个线程必须等第一、二个线程执行完毕它才能执行。 在这个时候,我们就需要调度线程了:让第一、二个线程执行完毕后,再执行第三个线程。 此时,就需要用semaphore了。

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
int a, b, c;
void geta()
{
a = calculatea();
semaphore_increase();
}

void getb()
{
b = calculateb();
semaphore_increase();
}


void getc()
{
semaphore_decrease(); //线程3在这里可能会停住等待
semaphore_decrease(); //线程3在这里可能会停住等待
c = a + b;
}

t1 = thread_create(geta);
t2 = thread_create(getb);
t3 = thread_create(getc);
thread_join(t3);

参考:信号量与互斥锁的区别

觉得文章有帮助可以打赏一下哦!