iOS端对应关系
Python:
ThreadPoolExecutor(max_workers)→ 管控线程池内部任务,只限制本池,靠worker线程数量做并发控制threading.Semaphore(DispatchSemaphore)→ 独立计数器对象,跨队列全局限流,只保护某一段代码
iOS这边也完完全全有一模一样的差异,两套工具,各自定位不同:
iOS 两套工具
①OperationQueue.maxConcurrentOperationCount
等价 PythonThreadPoolExecutor(max_workers)
letqueue=OperationQueue()queue.maxConcurrentOperationCount=3//最多同时3个Operation执行foriin0..<10{queue.addOperation{//任务逻辑}}- 管控范围:只管控丢进这个OperationQueue里面的任务。
别的队列(全局GCD队列、另一个OperationQueue)完全管不到。 - 原理:队列内部管理底层内核线程,最多同时跑N个任务;多余任务在队列内部排队。
- 特点:任务一加入Operation,只要开始执行,就占用一条底层线程,直到整个Operation结束。整个任务生命周期占用线程,不能只对其中一小段代码做闸门。
GCD本身没有直接设置最大并发的API!GCD全局并发队列不能限制并发数,GCD要限制并发只能两种方案:
- 用
DispatchSemaphore- 自定义
OperationQueue封装GCD任务
②DispatchSemaphore(GCD信号量)
等价 Pythonthreading.Semaphore
letsem=DispatchSemaphore(value:3)letglobalQ=DispatchQueue.global()foriin0..<10{globalQ.async{//这里代码不限流,可以疯狂并发heavyCalc()sem.wait()//仅仅这一段做闸门networkRequest()sem.signal()}}- 它是独立计数器对象,不管任务来自哪个队列:global队列、自定义队列、别的OperationQueue,只要调用wait()就会被限流。
- 可以做到:只有中间一小段临界代码被限流,前后代码不受限制。
- 代价:
wait()会阻塞当前内核线程,会引发线程爆炸。
核心差异(iOS版,和Python一一对应)
| iOS工具 | 管控什么 | 管控范围 | 特点 | Python对等物 |
|---|---|---|---|---|
| OperationQueue.maxConcurrentOperationCount | 任务整体,控制同时执行多少Operation | 仅本队列内部的任务 | 任务一旦执行,全程占住线程;不能只限制片段代码 | ThreadPoolExecutor(max_workers) |
| DispatchSemaphore | 一段临界区代码 | 所有队列,全局生效 | 可以只保护一小段代码;但是wait会阻塞内核线程,容易线程爆炸 | threading.Semaphore |
iOS场景举例,什么时候选哪个
场景1:所有任务统一提交到同一个队列,整个任务都需要并发控制
比如:批量下载图片,每一个operation完整就是一次下载。
👉直接用OperationQueue + maxConcurrentOperationCount。
简单干净,不要套信号量。
对应Python:全部任务丢进同一个ThreadPoolExecutor,设置max_workers。
场景2:多处不同来源的任务,都调用同一个接口,需要全局总并发限制
- A处:global队列异步
- B处:另一个OperationQueue
两处都会调用同一个网络接口,希望全局最多同时3个网络请求。
OperationQueue管不到跨队列。
👉这时用全局DispatchSemaphore。
对应Python:多处不同线程源,全局
threading.Semaphore。
场景3:一个任务,大部分逻辑不需要限流,只有中间一小块网络调用要限流
globalQ.async{doSomeHeavyLocalWork()//随便跑,不限流sem.wait()apiCall()//仅这里限流sem.signal()}Operation做不到,Operation粒度是整个任务。只能用DispatchSemaphore。
Python:线程池任务内部,局部用threading.Semaphore保护一小段。
iOS里一个非常经典错误(和Python一样)
OperationQueue设置maxConcurrentOperationCount=5,外面再套一个DispatchSemaphore(value=2)
queue.maxConcurrentOperationCount=5//添加很多operation//operation内部执行 sem.wait()会发生:底层一次性开5条内核线程,其中3条线程卡在sem.wait()原地阻塞休眠,线程被白白占用,浪费内核资源。
✅最佳实践:
- 如果任务全部交给本队列:直接把
maxConcurrentOperationCount设置成目标并发数,不要再叠加信号量。 - 只有跨队列、局部代码段限流,才上DispatchSemaphore。
上面全部是GCD / OperationQueue(基于内核线程)
Swift Concurrency(async/await)世界的对应
AsyncSemaphore👉 对应Pythonasyncio.Semaphore- Task是用户态对象,
await sem.wait()挂起Task,归还底层worker线程,不会阻塞内核线程,没有线程爆炸问题。 - 同样是保护一段临界区,可以跨多处Task做全局限流。
- Swift Concurrency没有类似ThreadPoolExecutor这种手动设置最大worker线程的API;worker线程数量由runtime自动等于CPU核心数,你不能手动调。
Swift Concurrency中,你想控制并发,几乎就只用
AsyncSemaphore。- Task是用户态对象,
⚠️ Swift Concurrency千万不要混用
DispatchSemaphore,会把协作池worker线程阻塞死。
把整套逻辑做一个总对照表
| 模型 | Python | iOS | 关键点 |
|---|---|---|---|
| 内核线程模型 (阻塞OS线程) | ThreadPoolExecutor(max_workers) | OperationQueue.maxConcurrentOperationCount | 只管自己池/队列;任务全程占用线程 |
| 内核线程模型 (阻塞OS线程) | threading.Semaphore | DispatchSemaphore | 独立计数器;跨队列;可只保护片段;会阻塞线程,线程爆炸风险 |
| 协程模型 (挂起,释放OS线程) | asyncio.Semaphore | AsyncSemaphore | 挂起Task/协程,不阻塞内核线程;做并发闸门 |
一句话总结
- Python线程池 ≈ iOS OperationQueue:管自己内部全部任务,粒度是整个任务。
- Python threading.Semaphore ≈ iOS DispatchSemaphore:独立计数器,可以跨来源、只保护一小段代码,但代价是阻塞内核线程。
- 协程那一套(asyncio / Swift Concurrency)没有“线程池手动设置max workers”,靠协程信号量做闸门,不会阻塞内核线程。
补充一个很多iOS开发者踩坑:GCD全局并发队列不能设置最大并发数,GCD全局队列会疯狂开线程,所以GCD做并发限流只能依靠DispatchSemaphore,这也是为什么老项目大量见到DispatchSemaphore。