如果在同一个队列上调度会发生什么?
What happens if dispatch on same queue?
如果需要检查 callbackQueue 是否为当前队列,我想了解以下情况。
请帮我清除这些场景,如果当前队列是回调队列会发生什么:
- callbackQueue 是 main 队列。
- callbackQueue 是 并发 队列。
- callbackQueue 是 serial 队列。
- (void)fetchWithCallbackQueue:(dispatch_queue_t)callbackQueue
{
dispatch_async(callbackQueue, ^{
});
}
我强烈推荐您观看这些视频。然后浏览我提供的示例,然后更改代码并尽可能多地使用它们。 我花了 3 年时间才完全适应 iOS 多线程,所以慢慢来:D
观看 this RWDevCon video 的前 3 分钟,如果您愿意,还可以观看更多内容。
同时观看 3:45 until 6:15。虽然我建议您完整观看此视频。
总结视频在我提到的持续时间内提出的要点:
线程和并发都是关于 source 队列和 destination。排队。
同步与异步是源队列的具体问题。
想一想您正在完成工作的高速公路的源队列和目标队列。
如果你async,那么这就像你让一辆车(必须运送东西)离开高速公路,然后继续让其他车在高速公路上行驶。
如果您执行 sync,那么这就像您让一辆汽车(必须运送东西)驶出高速公路,然后让所有其他汽车停在高速公路上,直到这辆汽车运送所有东西它的东西。
将运送货物的汽车想象成一段代码,开始和结束执行。
主队列发生的情况与串行队列发生的情况相同。它们都是串行队列。
因此,如果您已经在主线程上并分派到主线程并异步分派,那么您分派的任何内容都会转到队列的末尾
向您展示我的意思:您认为这将按什么顺序打印?您可以在 Playground 中轻松测试:
DispatchQueue.main.async {
print("1")
print("2")
print("3")
DispatchQueue.main.async {
DispatchQueue.main.async {
print("4")
}
print("5")
print("6")
print("7")
}
print("8")
}
DispatchQueue.main.async {
print("9")
print("10")
}
它将打印:
1
2
3
8
9
10
5
6
7
4
为什么?
主要是因为每次从main dispatch到main,block都会放在main队列的尾部
当您已经在主队列中时分派到主队列是您在应用程序的用户交互中看到的许多微小延迟的非常隐蔽的微妙原因。
如果使用 sync 分派到同一个 serial 队列会怎样?
僵局!参见 here
如果您使用 sync 分派到同一个 concurrent 队列,那么您将不会遇到死锁。但是所有其他线程只会等待您执行 sync 的那一刻。我在下面讨论过。
现在,如果您尝试调度到并发队列,那么如果您执行 sync,就像高速公路的示例一样,整个 5 车道高速公路是阻塞,直到汽车交付一切。但是在并发队列上同步是没用的,除非你正在做类似 .barrier
queue and are trying to solve a read-write 的问题。
但如果你在并发队列上同步,看看会发生什么:
let queue = DispatchQueue(label: "aConcurrentQueue", attributes: .concurrent)
for i in 0...4 {
if i == 3 {
queue.sync {
someOperation(iteration: UInt32(i))
}
} else {
queue.async {
someOperation(iteration: UInt32(i))
}
}
}
func someOperation(iteration: UInt32) {
sleep(1)
print("iteration", iteration)
}
将记录:
'3' 通常(不总是)第一个(或接近第一个),因为 sync
块在源队列上执行。正如 docs 在 sync
上所说:
As a performance optimization, this function executes blocks on the current thread whenever possible
其他迭代同时发生。每次您 运行 应用程序时,顺序可能会有所不同。这就是与并发相关的继承不可预测性。 4 将更接近于最后完成,而 0 将更接近于更快完成。所以像这样:
iteration 3
iteration 0
iteration 2
iteration 1
iteration 4
如果您在并发队列上执行 async,那么假设您的并发线程数量有限,例如5 然后 5 个任务将立即执行。只是每个给定的任务都会到达队列的末尾。这样做对于记录东西是有意义的。您可以有多个日志线程。一个线程记录位置事件,另一个记录购买等
一个很好的游乐场示例是:
let queue = DispatchQueue(label: "serial", attributes: .concurrent)
func delay(seconds: UInt32 ) {
queue.async {
sleep(seconds)
print(seconds)
}
}
for i in (1...5).reversed() {
delay(seconds: UInt32(i))
}
即使您先发送了 5 个,这也会打印
1
2
3
4
5
在您使用 dispatch_async
的示例中(或 Swift 中的 async
),这无关紧要。分派的块将简单地添加到相关队列的末尾,并在该队列可用时异步 运行。
但是,如果您使用了 dispatch_sync
(又名 Swift 中的 sync
),那么如果您从串行队列分派回自身,就会突然出现问题。通过从串行队列到自身的“同步”调度,代码将“死锁”。 (并且因为主队列是一个串行队列,从主队列到它自己的同步调度也表现出同样的问题。)dispatch_sync
表示“阻塞当前线程直到指定队列完成 运行ning 这个调度代码”,所以很明显,如果任何串行队列同步分派回自身,它就无法继续,因为它阻塞了您已将代码分派到的队列 运行.
请注意,任何阻塞 GCD API,例如 dispatch_semaphore_wait
and dispatch_group_wait
(在 Swift 中都称为 wait
),将遇到与同步调度相同的问题如果您在串行队列使用的同一线程上等待。
但是,在您的情况下,使用 dispatch_async
异步 分派 ,应该没有任何问题。
如果需要检查 callbackQueue 是否为当前队列,我想了解以下情况。
请帮我清除这些场景,如果当前队列是回调队列会发生什么:
- callbackQueue 是 main 队列。
- callbackQueue 是 并发 队列。
- callbackQueue 是 serial 队列。
- (void)fetchWithCallbackQueue:(dispatch_queue_t)callbackQueue
{
dispatch_async(callbackQueue, ^{
});
}
我强烈推荐您观看这些视频。然后浏览我提供的示例,然后更改代码并尽可能多地使用它们。 我花了 3 年时间才完全适应 iOS 多线程,所以慢慢来:D
观看 this RWDevCon video 的前 3 分钟,如果您愿意,还可以观看更多内容。
同时观看 3:45 until 6:15。虽然我建议您完整观看此视频。
总结视频在我提到的持续时间内提出的要点:
线程和并发都是关于 source 队列和 destination。排队。
同步与异步是源队列的具体问题。
想一想您正在完成工作的高速公路的源队列和目标队列。
如果你async,那么这就像你让一辆车(必须运送东西)离开高速公路,然后继续让其他车在高速公路上行驶。
如果您执行 sync,那么这就像您让一辆汽车(必须运送东西)驶出高速公路,然后让所有其他汽车停在高速公路上,直到这辆汽车运送所有东西它的东西。
将运送货物的汽车想象成一段代码,开始和结束执行。
主队列发生的情况与串行队列发生的情况相同。它们都是串行队列。
因此,如果您已经在主线程上并分派到主线程并异步分派,那么您分派的任何内容都会转到队列的末尾
向您展示我的意思:您认为这将按什么顺序打印?您可以在 Playground 中轻松测试:
DispatchQueue.main.async {
print("1")
print("2")
print("3")
DispatchQueue.main.async {
DispatchQueue.main.async {
print("4")
}
print("5")
print("6")
print("7")
}
print("8")
}
DispatchQueue.main.async {
print("9")
print("10")
}
它将打印:
1
2
3
8
9
10
5
6
7
4
为什么?
主要是因为每次从main dispatch到main,block都会放在main队列的尾部
当您已经在主队列中时分派到主队列是您在应用程序的用户交互中看到的许多微小延迟的非常隐蔽的微妙原因。
如果使用 sync 分派到同一个 serial 队列会怎样?
僵局!参见 here
如果您使用 sync 分派到同一个 concurrent 队列,那么您将不会遇到死锁。但是所有其他线程只会等待您执行 sync 的那一刻。我在下面讨论过。
现在,如果您尝试调度到并发队列,那么如果您执行 sync,就像高速公路的示例一样,整个 5 车道高速公路是阻塞,直到汽车交付一切。但是在并发队列上同步是没用的,除非你正在做类似 .barrier
queue and are trying to solve a read-write 的问题。
但如果你在并发队列上同步,看看会发生什么:
let queue = DispatchQueue(label: "aConcurrentQueue", attributes: .concurrent)
for i in 0...4 {
if i == 3 {
queue.sync {
someOperation(iteration: UInt32(i))
}
} else {
queue.async {
someOperation(iteration: UInt32(i))
}
}
}
func someOperation(iteration: UInt32) {
sleep(1)
print("iteration", iteration)
}
将记录:
'3' 通常(不总是)第一个(或接近第一个),因为 sync
块在源队列上执行。正如 docs 在 sync
上所说:
As a performance optimization, this function executes blocks on the current thread whenever possible
其他迭代同时发生。每次您 运行 应用程序时,顺序可能会有所不同。这就是与并发相关的继承不可预测性。 4 将更接近于最后完成,而 0 将更接近于更快完成。所以像这样:
iteration 3
iteration 0
iteration 2
iteration 1
iteration 4
如果您在并发队列上执行 async,那么假设您的并发线程数量有限,例如5 然后 5 个任务将立即执行。只是每个给定的任务都会到达队列的末尾。这样做对于记录东西是有意义的。您可以有多个日志线程。一个线程记录位置事件,另一个记录购买等
一个很好的游乐场示例是:
let queue = DispatchQueue(label: "serial", attributes: .concurrent)
func delay(seconds: UInt32 ) {
queue.async {
sleep(seconds)
print(seconds)
}
}
for i in (1...5).reversed() {
delay(seconds: UInt32(i))
}
即使您先发送了 5 个,这也会打印
1
2
3
4
5
在您使用 dispatch_async
的示例中(或 Swift 中的 async
),这无关紧要。分派的块将简单地添加到相关队列的末尾,并在该队列可用时异步 运行。
但是,如果您使用了 dispatch_sync
(又名 Swift 中的 sync
),那么如果您从串行队列分派回自身,就会突然出现问题。通过从串行队列到自身的“同步”调度,代码将“死锁”。 (并且因为主队列是一个串行队列,从主队列到它自己的同步调度也表现出同样的问题。)dispatch_sync
表示“阻塞当前线程直到指定队列完成 运行ning 这个调度代码”,所以很明显,如果任何串行队列同步分派回自身,它就无法继续,因为它阻塞了您已将代码分派到的队列 运行.
请注意,任何阻塞 GCD API,例如 dispatch_semaphore_wait
and dispatch_group_wait
(在 Swift 中都称为 wait
),将遇到与同步调度相同的问题如果您在串行队列使用的同一线程上等待。
但是,在您的情况下,使用 dispatch_async
异步 分派 ,应该没有任何问题。