Python異步編程中任務(wù)調(diào)度的實(shí)踐
背景:
在這時(shí)fastapi web框架時(shí),需要一個(gè)實(shí)時(shí)任務(wù)調(diào)度、一個(gè)定時(shí)任務(wù)調(diào)度。
這是我首先想到的是,使用?fastapi.BackgroundTasks?完成 實(shí)時(shí)任務(wù)調(diào)用,使用AsyncIOScheduler 完成定時(shí)任務(wù)調(diào)度。以下是我踩過的一些坑:
一、同時(shí)使用多個(gè)任務(wù)調(diào)度,導(dǎo)致阻塞和死鎖
先說結(jié)論:
? ? 當(dāng)我觸發(fā)定時(shí)任務(wù)后,定時(shí)任務(wù)執(zhí)行時(shí)會(huì)去注冊(cè)后臺(tái)任務(wù),此時(shí)的后臺(tái)任務(wù)中如果有IO阻塞(文件IO),整個(gè)AsyncIOScheduler的任務(wù)調(diào)度會(huì)被阻塞和死鎖。
原因:
? ? AsyncIOScheduler 線程池來執(zhí)行異步任務(wù),并在其中使用 BackgroundTasks().add_task() 將異步后臺(tái)任務(wù)添加到后臺(tái)任務(wù)隊(duì)列中以等待異步執(zhí)行。由于任務(wù)調(diào)度是在 AsyncIOScheduler 中完成的,異步 I/O 操作可能會(huì)導(dǎo)致阻塞和死鎖,因?yàn)槭录h(huán)不知道任務(wù)何時(shí)執(zhí)行完成,從而可能會(huì)導(dǎo)致掛起。
對(duì)此我做了以下三種嘗試:
(一)嘗試:
1.fastapi中,我有一個(gè)AsyncIOScheduler 的job(使用的線程池),
2.這個(gè)job執(zhí)行的代碼有 fastapi.BackgroundTasks().add_task(asyncio.gather(*tasks))也就是添加后臺(tái)任務(wù)去執(zhí)行asyncio.gather(*tasks)
3.添加的這些tasks中有 一句aiofiles.open(self.full_path, mode='r') ,我debug看時(shí)執(zhí)行到這里就會(huì)一直掛起了。
分析:
在第一種情況下,使用了?AsyncIOScheduler 線程池來執(zhí)行異步任務(wù),并在其中使用 BackgroundTasks().add_task() 將異步后臺(tái)任務(wù)添加到后臺(tái)任務(wù)隊(duì)列中以等待異步執(zhí)行。由于任務(wù)調(diào)度是在 AsyncIOScheduler 中完成的,您的異步 I/O 操作可能會(huì)導(dǎo)致阻塞和死鎖,因?yàn)槭录h(huán)不知道任務(wù)何時(shí)執(zhí)行完成,從而可能會(huì)導(dǎo)致掛起。
(二)嘗試:
1.fastapi中,我有一個(gè)AsyncIOScheduler 的job(使用的線程池)
2.這個(gè)job執(zhí)行的代碼不使用fastapi.BackgroundTasks().add_task(),而是直接執(zhí)行await asyncio.gather(*tasks)
3.添加的這些tasks中有 一句aiofiles.open(self.full_path, mode='r') ,但這里就不會(huì)掛起。
分析:
在第二種情況下,手動(dòng)調(diào)用了異步 I/O 操作,因此,任務(wù)會(huì)在同一事件循環(huán)中按順序執(zhí)行。因此,在阻塞 I/O 操作執(zhí)行時(shí),它會(huì)掛起當(dāng)前協(xié)程,等待I/O操作完成,不會(huì)導(dǎo)致任務(wù)掛起。
(三)嘗試:
1.fastapi中,我不使用AsyncIOScheduler
2.直接請(qǐng)求調(diào)用fastapi.BackgroundTasks().add_task(asyncio.gather(*tasks))也就是添加后臺(tái)任務(wù)去執(zhí)行asyncio.gather(*tasks)
3.添加的這些tasks中有 一句aiofiles.open(self.full_path, mode='r') ,這時(shí)也不會(huì)掛起
分析:
在第三種情況下,完全跳過了使用 AsyncIOScheduler ,并使用 BackgroundTasks().add_task() 來調(diào)度一組異步任務(wù)。由于異步任務(wù)在同一個(gè)事件循環(huán)環(huán)境中執(zhí)行,因此異步I/O操作不會(huì)導(dǎo)致其他異步任務(wù)的阻塞和死鎖。
總結(jié):
在三種嘗試中,都使用了 aiofiles 庫進(jìn)行文件 I/O 操作。由于這個(gè)庫提供了一些異步 I/O 操作,它可以更好的協(xié)同運(yùn)行在異步編程模型下,避免I/O阻塞。
因此,由于 AsyncIOScheduler 的原因,第一種嘗試失敗。而在第二種和第三種嘗試中,任務(wù)都在相同的事件循環(huán)中運(yùn)行,并使用異步 I/O 操作對(duì)文件進(jìn)行讀取和寫入,因此阻塞操作不會(huì)導(dǎo)致死鎖或掛起的現(xiàn)象。