我认为我知道如何使用DispatchGroup来理解这个问题,我尝试过:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
performUsingGroup()
}
func performUsingGroup() {
let dq1 = DispatchQueue.global(qos: .userInitiated)
let dq2 = DispatchQueue.global(qos: .userInitiated)
let group = DispatchGroup()
group.enter()
dq1.async {
for i in 1...3 {
print("\(#function) DispatchQueue 1: \(i)")
}
group.leave()
}
group.wait()
dq2.async {
for i in 1...3 {
print("\(#function) DispatchQueue 2: \(i)")
}
}
group.notify(queue: DispatchQueue.main) {
print("done by group")
}
}
}
结果 - 预期 - 是:
performUsingGroup() DispatchQueue 1: 1
performUsingGroup() DispatchQueue 1: 2
performUsingGroup() DispatchQueue 1: 3
performUsingGroup() DispatchQueue 2: 1
performUsingGroup() DispatchQueue 2: 2
performUsingGroup() DispatchQueue 2: 3
done by group
为了使用信号量,我实现了:
func performUsingSemaphore() {
let dq1 = DispatchQueue.global(qos: .userInitiated)
let dq2 = DispatchQueue.global(qos: .userInitiated)
let semaphore = DispatchSemaphore(value: 1)
dq1.async {
semaphore.wait()
for i in 1...3 {
print("\(#function) DispatchQueue 1: \(i)")
}
semaphore.signal()
}
dq2.async {
semaphore.wait()
for i in 1...3 {
print("\(#function) DispatchQueue 2: \(i)")
}
semaphore.signal()
}
}
并在viewDidLoad
方法中调用它。结果是:
performUsingSemaphore() DispatchQueue 1: 1
performUsingSemaphore() DispatchQueue 1: 2
performUsingSemaphore() DispatchQueue 1: 3
performUsingSemaphore() DispatchQueue 2: 1
performUsingSemaphore() DispatchQueue 2: 2
performUsingSemaphore() DispatchQueue 2: 3
从概念上讲,DispachGroup和Semaphore都有同样的目的(除非我误解了一些东西)。
老实说,我不熟悉:何时使用信号量,特别是在与DispachGroup合作时 - 可能 - 处理问题。
我错过了什么部分?
答案 0 :(得分:14)
从概念上讲,DispachGroup和Semaphore都有同样的目的(除非我误解了一些东西)。
以上并非完全正确。您可以使用信号量来执行与调度组相同的操作,但它更通用。
当您有大量想要做的事情可以一次性完成时使用调度组,但是您需要等待它们全部完成才能完成其他操作。
信号量可以用于上述内容,但它们是通用同步对象,也可以用于许多其他目的。信号量的概念不仅限于Apple,而且可以在许多操作系统中找到。
通常,信号量的值为非负整数和两个操作:
等待如果值不为零,则递减它,否则阻塞,直到信号发出信号。
signal 如果有线程在等待,请取消阻止其中一个,否则增加该值。
不用说两个操作都必须是线程安全的。在过去,当你只有一个CPU时,你只需要在操作值和等待线程的队列时禁用中断。如今,由于有多个CPU内核和片上缓存等,它更复杂。
在任何情况下,您都可以使用信号量,而且资源最多可以同时被N个线程访问。您将信号量的初始值设置为N,然后等待它的前N个线程不被阻止,但是下一个线程必须等到前N个线程中的一个已发信号通知信号量。最简单的情况是N = 1.在这种情况下,信号量的行为类似于互斥锁。
信号量可用于模拟调度组。你从0开始sempahore,开始所有的任务 - 跟踪你已经开始的数量,并等待信号量多次。每个任务必须在信号完成时发出信号。
然而,有一些陷阱。例如,您需要一个单独的计数才能知道要等待多少次。如果您希望在开始等待后能够向组中添加更多任务,则只能在互斥锁保护块中更新计数,这可能会导致死锁问题。另外,我认为信号量的Dispatch实现可能容易受到优先级反转的攻击。当高优先级线程等待低优先级抓取的资源时,发生优先级倒置。高优先级线程被阻塞,直到低优先级线程释放资源。如果存在中等优先级线程,则可能永远不会发生这种情况。
你可以用其他更高级别的同步抽象可以做的信号量做任何事情,但做正确的事情往往是一件棘手的事情。更高级别的抽象(希望)是经过精心编写的,你应该优先使用它们来推动你自己的"如果可能的话,用信号量实现。
答案 1 :(得分:8)
信号量和群组在某种意义上具有相反的语义。两者都保持计数。使用信号量,当计数非零时,允许wait
继续。对于组,当计数为零时,允许wait
继续。
当您希望一次为某些共享资源上运行的线程数设置最大值时,信号量非常有用。一个常见用途是当最大值为1时,因为共享资源需要独占访问。
当你需要知道一堆任务何时完成时,一个小组非常有用。
答案 2 :(得分:6)
使用信号量限制给定时间的并发工作量。使用组等待任意数量的并发工作完成执行。
如果你想为每个队列提交三个作业,那么它应该是
func performUsingGroup() {
let dq1 = DispatchQueue.global(qos: .default)
let dq2 = DispatchQueue.global(qos: .default)
let group = DispatchGroup()
for i in 1...3 {
group.enter()
dq1.async {
print("\(#function) DispatchQueue 1: \(i)")
group.leave()
}
}
for i in 1...3 {
group.enter()
dq2.async {
print("\(#function) DispatchQueue 2: \(i)")
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print("done by group")
}
}
和
func performUsingSemaphore() {
let dq1 = DispatchQueue.global(qos: .default)
let dq2 = DispatchQueue.global(qos: .default)
let semaphore = DispatchSemaphore(value: 1)
for i in 1...3 {
dq1.async {
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
print("\(#function) DispatchQueue 1: \(i)")
semaphore.signal()
}
}
for i in 1...3 {
dq2.async {
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
print("\(#function) DispatchQueue 2: \(i)")
semaphore.signal()
}
}
}
答案 3 :(得分:3)
一个典型的信号量用例是一个可以从不同线程同时调用的函数,并使用不应该同时从多个线程调用的资源:
func myFunction() {
semaphore.wait()
// access the shared resource
semaphore.signal()
}
在这种情况下,您可以从不同的线程调用myFunction
,但他们无法同时到达锁定的资源。其中一个必须等到第二个完成它的工作。
信号量保持计数,因此您实际上可以允许给定数量的线程同时输入您的函数。
典型的共享资源是文件的输出。
信号量不是解决此类问题的唯一方法。例如,您也可以将代码添加到串行队列中。
信号量是低级原语,很可能它们在GCD中被大量使用。
另一个典型示例是producer-consumer problem,其中signal
和wait
调用实际上是两个不同函数的一部分。一个产生数据,另一个产生数据。
答案 4 :(得分:1)
以上 Jano 和 Ken 的回答是正确的,关于 1) 使用信号量来限制一次发生的工作量 2) 使用调度组,以便在组中的所有任务完成时通知该组完成。例如,您可能希望并行下载大量图像,但由于您知道它们是大量图像,因此您希望一次只能下载两次,因此您可以使用信号量。您还希望在所有下载(假设有 50 个)完成时收到通知,因此您使用 DispatchGroup。因此,这不是在两者之间进行选择的问题。根据您的目标,您可以在同一个实现中使用一个或两个。 Ray Wenderlich 网站上的并发教程中提供了此类示例:
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .utility)
let semaphore = DispatchSemaphore(value: 2)
let base = "https://yourbaseurl.com/image-id-"
let ids = [0001, 0002, 0003, 0004, 0005, 0006, 0007, 0008, 0009, 0010, 0011, 0012]
var images: [UIImage] = []
for id in ids {
guard let url = URL(string: "\(base)\(id)-jpeg.jpg") else { continue }
semaphore.wait()
group.enter()
let task = URLSession.shared.dataTask(with: url) { data, _, error in
defer {
group.leave()
semaphore.signal()
}
if error == nil,
let data = data,
let image = UIImage(data: data) {
images.append(image)
}
}
task.resume()
}