dispatch semaphore

简述

含义

dispatch_semaphore_t/DispatchSemaphore

表示计数信号量。

函数

Objective-C

  1. dispatch_semaphore_create
  2. dispatch_semaphore_wait
  3. dispatch_semaphore_signal

Swift

  1. public init(value: Int)
  2. public func wait()public func wait(timeout: DispatchTime) -> DispatchTimeoutResultpublic func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult
  3. public func signal() -> Int

dispatch_semaphore_create

dispatch_semaphore_t dispatch_semaphore_create(long value)

创建计数信号量,value为其起始值。不能传入小于0的值。如果创建失败,则返回NULL

当两个线程需要协调特定事件的完成时,传入0
传入大于0的值,在管理有限资源池时是非常有用的,传入的值等于资源池的大小。

当不再需要信号量时,应当调用dispatch_release释放信号量(ARC下不能使用该函数)。

dispatch_semaphore_wait

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)

返回0,表示成功;否则,表示超时。

减小计数信号量。如果结果值小于0,这个函数在返回之前,会一直等待信号的发生。

dispatch_semaphore_signal

long dispatch_semaphore_signal(dispatch_semaphore_t dsema)

增大计数信号量。如果之前的值小于0,该函数会唤醒一个正在使用dispatch_semaphore_wait函数等待的线程。

适用场景

在上面的dispatch_semaphore_create解释中,已经提到过。

  1. 对线程的运行进行协调
  2. 管理有限资源池

实践

协调线程

也就是控制两个线程中特定事件的完成顺序。应用场景,比如经典的『生产-消费模型』。

在调用dispatch_semaphore_create函数时,必须传入0

以下面的示例为基础:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

Thread.detachNewThreadSelector(#selector(_thread1(arg:)), toTarget: self, with: nil)
Thread.detachNewThreadSelector(#selector(_thread2(arg:)), toTarget: self, with: nil)
}

@objc private func _thread1(arg: Any?) {
for i in 0..<5 {
print(#function + " \(i)")
}
}

@objc private func _thread2(arg: Any?) {
for i in 0..<5 {
print(#function + " \(i)")
}
}

}

运行上面的代码,结果可能如下:

1
2
3
4
5
6
7
8
9
10
_thread2(arg:) 0
_thread1(arg:) 0
_thread2(arg:) 1
_thread1(arg:) 1
_thread2(arg:) 2
_thread1(arg:) 2
_thread2(arg:) 3
_thread1(arg:) 3
_thread2(arg:) 4
_thread1(arg:) 4

很明显,两个任务是交错运行的。

为了先执行完_thread2中的任务,再执行_thread1中的任务,使用信号量,就可以这样写:

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
class ViewController: UIViewController {

let sema: DispatchSemaphore = DispatchSemaphore(value: 0)

override func viewDidLoad() {
super.viewDidLoad()

Thread.detachNewThreadSelector(#selector(_thread1(arg:)), toTarget: self, with: nil)
Thread.detachNewThreadSelector(#selector(_thread2(arg:)), toTarget: self, with: nil)
}

@objc private func _thread1(arg: Any?) {
self.sema.wait()
for i in 0..<5 {
print(#function + " \(i)")
}
}

@objc private func _thread2(arg: Any?) {
for i in 0..<5 {
print(#function + " \(i)")
}
self.sema.signal()
}

}

运行结果就会是:

1
2
3
4
5
6
7
8
9
10
_thread2(arg:) 0
_thread2(arg:) 1
_thread2(arg:) 2
_thread2(arg:) 3
_thread2(arg:) 4
_thread1(arg:) 0
_thread1(arg:) 1
_thread1(arg:) 2
_thread1(arg:) 3
_thread1(arg:) 4

管理资源池

也就是协调有限资源的使用。

在创建信号量时,传入的值,要与可用的资源的数量一致。

拿园区小轿车停车为例,停车位属于有限的资源。

当停车位被占用后,其它小轿车是不能再占用该停车位的。

当园区中没有可用的停车位时,其它小轿车就得等待停车位上的小轿车离开后,才能去占用那个停车位。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Car: NSObject {
let identifier: String

init(identifier: String) {
self.identifier = identifier
}

override var description: String {
get {
return "{小车[\(self.identifier)]}"
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
停车位
*/
class ParkingSpace: NSObject {
let identifier: String
weak var car: Car? = nil

var available: Bool {
get {
return self.car == nil
}
}

init(identifier: String) {
self.identifier = identifier
}

override var description: String {
get {
return "{停车位[\(self.identifier)]}"
}
}
}
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
53
54
55
56
57
58
59
60
61
62
63
64
class ViewController: UIViewController {

let cars: [Car] = [
Car.init(identifier: "京A 11111"),
Car.init(identifier: "京B 22222"),
Car.init(identifier: "京C 33333"),
Car.init(identifier: "京D 44444"),
Car.init(identifier: "京E 55555"),
]
let parkingSpaces: [ParkingSpace] = [
ParkingSpace.init(identifier: "001"),
ParkingSpace.init(identifier: "002"),
ParkingSpace.init(identifier: "003"),
]
let parkingSpaceSemaphore: DispatchSemaphore
let parkingSemaphore: DispatchSemaphore = DispatchSemaphore(value: 1)

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.parkingSpaceSemaphore = DispatchSemaphore(value: self.parkingSpaces.count)

super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

required init?(coder aDecoder: NSCoder) {
self.parkingSpaceSemaphore = DispatchSemaphore(value: self.parkingSpaces.count)

super.init(coder: aDecoder)
}

override func viewDidLoad() {
super.viewDidLoad()

for car in self.cars {
Thread.detachNewThreadSelector(#selector(_carParkingThread(arg:)), toTarget: self, with: car)
}
}

@objc private func _carParkingThread(arg: Any?) {
if let car = arg as? Car {
print("\(car)要来停车啦")
self.parkingSpaceSemaphore.wait()
// 由于同时可能存在多个可用的停车位,如果不加锁,查找到的停车位可能是同一个,两辆车不能同时占用同一个停车位
self.parkingSemaphore.wait()
var availablePSs: [ParkingSpace] = []
for ps in self.parkingSpaces {
if ps.available {
availablePSs.append(ps)
}
}
// 由于是通过parkingSpaceSemaphore来通知有可用的停车位的,因此availablePSs中总会有元素,并不需要检查availablePSs.count是否大于0
let ps = availablePSs[Int(arc4random_uniform(UInt32(availablePSs.count)))]
ps.car = car
print("\(car)占用了\(ps)")
self.parkingSemaphore.signal()
let ti = 1 + arc4random_uniform(5)
sleep(ti)
ps.car = nil
// 模拟为分钟
print("\(ti)分钟后,\(car)离开了\(ps)")
self.parkingSpaceSemaphore.signal()
}
}

}

运行结果可能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{小车[京B 22222]}要来停车啦
{小车[京A 11111]}要来停车啦
{小车[京D 44444]}要来停车啦
{小车[京C 33333]}要来停车啦
{小车[京E 55555]}要来停车啦
{小车[京B 22222]}占用了{停车位[003]}
{小车[京A 11111]}占用了{停车位[002]}
{小车[京D 44444]}占用了{停车位[001]}
1分钟后,{小车[京D 44444]}离开了{停车位[001]}
{小车[京C 33333]}占用了{停车位[001]}
4分钟后,{小车[京A 11111]}离开了{停车位[002]}
{小车[京E 55555]}占用了{停车位[002]}
5分钟后,{小车[京B 22222]}离开了{停车位[003]}
5分钟后,{小车[京C 33333]}离开了{停车位[001]}
3分钟后,{小车[京E 55555]}离开了{停车位[002]}

dispatch semaphore
https://daniate.github.io/2017/02/23/dispatch semaphore/
作者
Daniate
发布于
2017年2月23日
许可协议