ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

信号量(DispatchSemaphore vs AsyncSemaphore)、swift协作式线程池 and python信号量

信号量(DispatchSemaphore vs AsyncSemaphore)、swift协作式线程池 and python信号量 通俗讲清楚 Task、挂起(suspend)、不阻塞内核线程先把两个东西拆开对比用你熟悉的GCD做参照物。1、DispatchSemaphoreGCD老信号量底层操作对象操作系统内核线程OS ThreadletsemDispatchSemaphore(value:2)sem.wait()当计数为0wait()会调用操作系统内核把当前正在跑的这条内核线程直接休眠、卡住。这条线程原地不动什么活都干不了内核把它调度出去。直到别的线程调用signal()内核再把这条线程唤醒。代价占住一条操作系统线程资源干等不能干别的。如果并发网络请求很多GCD为了不卡住会不停新建更多内核线程 →线程爆炸。2、Swift Concurrency 的 Task AsyncSemaphore协程任务Task ≠ 操作系统内核线程Task 是Swift运行时在堆上分配的一个普通对象保存局部变量、代码执行到哪一行、状态运行/挂起。你可以创建成千上万个Task不会创建成千上万个内核线程。Swift有一个协作式线程池线程数量基本等于CPU物理核数比如手机8核池子就8条工作内核线程不会疯狂新增线程。await 挂起suspend重点不是block阻塞当执行到await sem.wait()AsyncSemaphore许可用完的时候当前这个Task立刻被挂起。Swift运行时把这个Task所有现场变量、执行位置保存到堆内存。把这条正在使用的内核线程还给线程池释放出来这条内核线程马上拿去跑另外一个就绪的Task。被挂起的Task就安安静静躺在内存队列里等待它不占任何内核线程等到别的Task调用signal()释放许可这个等待的Task标记为就绪线程池里面一旦有空闲内核线程就拿过来继续跑这个Task剩下的代码。重点等待的时候Task被挂起内核线程已经被回收去干别的活没有线程被卡住休眠。这就是我说的操作Task用户态对象不碰、不阻塞操作系统内核线程。举个生活化比喻DispatchSemaphore(GCD)会议室有2把椅子许可。来5个人任务。没椅子的人就原地站死等人内核线程占住啥别的活不干。人多了就不停招人新建内核线程来排队站着。AsyncSemaphore(Swift Concurrency)会议室2把椅子。来5个人Task。没椅子的人全部坐到大厅等候区堆内存队列人离开工位释放内核线程工位给其他人用。椅子空出来再叫大厅的人上来坐椅子干活。等候区坐1000个人都没事不会占用工位。大厅的人就是被挂起的Task不占用工位内核线程只是内存对象。上图逻辑Task A走到await挂起释放线程线程立刻跑Task C等条件就绪再拿一条空闲线程回来继续跑Task A。3、非常关键的认知纠正Task本身不是线程Task是一段要执行的作业它只是临时借用线程池里面的内核线程跑一会儿遇到await挂起点就把线程还回去。同一个Task前后执行甚至可能跑在不同的内核线程上。DispatchSemaphore.wait()阻塞内核线程内核层面sleepawait AsyncSemaphore.wait()挂起Task用户态Runtime管理释放内核线程内核完全不知道这个Task在等。4、为什么GCD容易线程爆炸Swift Concurrency可以开成千上万TaskGCD全局并发队列只要有任务在wait()把线程阻塞住GCD就会新建内核线程保证后面任务有线程跑越阻塞线程越多。Swift ConcurrencyTask遇到await就归还线程不会占住线程死等线程池数量固定等于CPU核数不会无限创建线程哪怕几百上千Task同时等待也只消耗内存不消耗内核线程资源。5、和Python做个一一对应打通你前面MinerU那套SwiftPython行为DispatchSemaphorethreading.Semaphore阻塞操作系统内核线程必须多线程才有意义Task AsyncSemaphore协程 asyncio.Semaphore挂起Task/协程释放OS线程等待阶段不占用内核线程MinerU第三方MCP大量协程asyncio.Semaphore限流。成千上百协程对象但是OS就一条主线程等待网络的时候线程空闲处理别的协程。和Swift TaskAsyncSemaphore逻辑一模一样。6、一个高频踩坑Swift❌千万不要在async函数内部调用GCD的DispatchSemaphore.wait()Task{letsemDispatchSemaphore(value:0)sem.wait()// ❌灾难直接把线程池的一条内核线程给阻塞占死不归还}这里wait()会把协作线程池的worker线程永久卡住线程池可用线程变少严重直接卡死整个并发运行时。Swift async环境里面要限流就用AsyncSemaphore绝对不要用GCD信号量。简短总结用户态协程Task不碰内核线程Task是Swift运行时用户态维护的作业对象保存在堆内存不是操作系统内核线程。当await AsyncSemaphore.wait()拿不到许可Task被挂起保存现场把正在使用的内核线程归还线程池线程可以去跑别的任务。等待期间没有任何操作系统线程被休眠、阻塞内核完全不知道这个Task在等待全部逻辑在Swift Runtime用户态完成排队。只有当Task被唤醒要继续执行的时候才会再次“借”一条内核线程跑代码。而老的DispatchSemaphore直接调用操作系统内核让真正的内核线程休眠卡住这就是两者本质鸿沟。
返回列表