例如,有一个1000倍的循环。最大值是多少才能使它快速,有效,并且不会导致死锁?
let group = DispatchGroup()
let queue = DispatchQueue(label: "com.num.loop", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 4)
for i in 1...1000 {
semaphore.wait()
group.enter()
queue.async(group: group, execute: {
doWork(i)
group.leave()
semaphore.signal()
})
}
group.notify(queue: DispatchQueue.main) {
// go on...
}
以下几点观察:
>
您永远不希望超过每个QoS的最大GCD工作线程数。如果你超过这个值,你可能会在你的应用程序中遇到阻塞。我最后一次检查,这个限制是64个线程。
话虽如此,但设备上超过内核的数量通常没有什么好处。
通常,我们会让GCD使用concurrentperform
计算出并发线程的最大数量,这是为设备自动优化的。它还消除了对任何信号量或组的需要,通常导致更自然的读取代码:
DispatchQueue.global().async {
DispatchQueue.concurrentPerform(iterations: 1000) { i in
doWork(i)
}
DispatchQueue.main.async {
// go on...
}
}
concurrentPerform
将并行运行1,000个迭代,但将并发线程的数量限制在适合您的设备的水平,从而消除了对信号量的需要。但是concurrentperform
本身是同步的,在完成所有迭代之前不会继续,因此不需要分派组。因此,将整个concurrentperform
分派到某个后台队列,完成后,只需执行“完成代码”(或者,在您的情况下,将该代码分派回主队列)。
虽然我在上面讨论了concurrentperform
,但只有当doWork
同步执行其任务(例如,某些计算操作)时,它才起作用。如果它发起的东西本身是异步的,那么我们必须回到这个信号量/组技术。(或者,更好的做法是使用异步operation
子类和具有合理maxConcurrentOperationCount
队列,或者将flatmap(maxPublishers:)
与合理的计数限制相结合)。
关于合理的阈值,在这种情况下,没有神奇的数字。你只需要执行一些经验测试,在内核数量和应用程序中可能发生的其他事情之间找到合理的平衡。例如,对于网络请求,我们通常使用4或6作为最大计数,这不仅考虑到超过该计数的好处会减少,而且考虑到如果成千上万的用户碰巧同时提交过多的并发请求对服务器的影响。
在“使其快速”方面,“应该允许并发运行多少次迭代”的选择只是决策制定过程的一部分。更关键的问题很快就变成了确保doWork
完成足够的工作,以证明并发模式引入的适度开销是合理的。
例如,如果处理一个1,000×1,000像素的图像,您可以执行1,000,000次迭代,每个迭代处理一个像素。但如果你这样做,你可能会发现它实际上比你的非并发翻译慢。相反,您可能有1,000次迭代,每个迭代处理1,000个像素。或者您可能有100次迭代,每次处理10,000个像素。这种被称为“跨步”的技术通常需要一点经验研究,以便在一个人将执行多少次迭代和每个迭代完成多少工作之间找到正确的平衡。(而且,顺便说一句,当多个线程争夺相邻的内存访问地址时,这种跨步模式通常还可以防止高速缓存晃动。)
首先想到的问题是,为什么我们需要信号量? 一个简单的答案,以保护多个进程共享的关键/共同区域。 假设多个进程正在使用相同的代码区域,如果所有人都想并行访问,那么结果是重叠的。 例如,多个用户仅使用一台打印机(通用/关键部分),例如个用户,同时给予个作业,如果所有作业并行启动,则一个用户输出与另一个用户输出重叠。 因此,我们需要使用信号量来保护这个信号,即当一个进程正在运行时锁定关键部分,并在完成时
信号量 信号量是一种同步互斥机制的实现,普遍存在于现在的各种操作系统内核里。相对于spinlock 的应用对象,信号量的应用对象是在临界区中运行的时间较长的进程。等待信号量的进程需要睡眠来减少占用 CPU 的开销。参考教科书“Operating Systems Internals and Design Principles”第五章“同步互斥”中对信号量实现的原理性描述: struct semaph
一个线程发送信号量,另外一个线程接收信号量 一个线程发送信号量,另外一个线程接收信号量 源码/* * Copyright (c) 2006-2018, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2018-08-24 yangjie
信号量接口 结构体 struct rt_semaphore 信号量控制块 更多... 类型定义 typedef struct rt_semaphore * rt_sem_t 信号量类型指针定义 函数 rt_err_t rt_sem_init (rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag
信号量 这是本章的第三部分 chapter,本章描述了内核中的同步原语,在之前的部分我们见到了特殊的 自旋锁 - 排队自旋锁。 在更前的 部分 是和 自旋锁 相关的描述。我们将描述更多同步原语。 在 自旋锁 之后的下一个我们将要讲到的 内核同步原语是 信号量。我们会从理论角度开始学习什么是 信号量, 然后我们会像前几章一样讲到Linux内核是如何实现信号量的。 好吧,现在我们开始。 介绍Linux
该函数的操作对象为信号量,而非信号量集合。这是一个原子操作。 为semaphore operate的缩写 函数原型 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, struct sembuf *sops, unsigned nsops); int semtimedop(int
控制信号量集合、信号量 函数原型 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...); 参数 可以有三个参数,也可以有四个参数(利用的可变参数个数的函数定义)。 semid是信号量集合的标识符,一般由semget函数返回。 s
System V提供的三种IPC进制,有异曲同工之妙。 semget 创建信号量结合 函数原型 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); 参数 参数含义,与msgget类似,只是比它多了第二个参数。 key为ftok函数