接上一篇Ansible和celery的结合,在celery的tasks.py文件里为了实现并发不阻塞的需求,用到了多进程
接上一篇Ansible和celery的結合,在celery的tasks.py文件里為了實現并發不阻塞的需求,用到了多進程
舉例如下:tasks.py文件如下:
import signal from multiprocessing import Process from django.db import connectionsdef close_old_connecttions():connections.close_all()@shared_task def function():signal.signal(signal.SIGCHLD, signal.SIG_IGN)close_old_connections():process = Process(target=_function,args=(x,x,x))process.start()# process.join()def _function():xxxreturn Ture先說為什么重新開子進程
- function函數是執行在celery起的主進程里的,一般-n 是4,就是有4個進程,能同時處理4個請求,如果同時有10個請求,且,每一個任務都要執行十分鐘,那么就會產生阻塞,重新起子進程的過程可能只需要幾秒,就不會產生阻塞,前提是不加join
解釋signal那一行
- signal.signal中的第一個參數是要監聽的狀態,signal.SIGCHLD為子進程死亡或退出,第二個參數是監聽到前面的狀態時采用的處理操作,signal.SIG_IGN的意思是什么都不做。
- 這個什么也不做并不是正真的什么也不做,如果沒有這一行,系統判定父進程沒有定義子進程退出的處理函數,就是僵尸進程,但是如果定義了就算這個操作是什么都不做,系統也知道父進程定義了,就不會產生僵尸進程
僵尸進程和孤兒進程
- 子進程退出,系統會回收內存空間,但是會留下pcb控制塊,里面會有一些PID信息和退出信息,如果父進程沒有回收執行完畢的子進程,就會產生僵尸進程,產生資源浪費
- 孤兒進程是父進程先于子進程退出了,這是子進程會是孤兒進程,由init進程負責回收(孤兒進程無害)
兩種避免僵尸進程產生的方法:兩次fork方法和signal信號方法
-
參考:https://yhyr.github.io/2018/06/06/%E5%9F%BA%E4%BA%8EPython%E5%88%9D%E6%8E%A2Linux%E4%B8%8B%E7%9A%84%E5%83%B5%E5%B0%B8%E8%BF%9B%E7%A8%8B%E5%92%8C%E5%AD%A4%E5%84%BF%E8%BF%9B%E7%A8%8B-%E4%BA%8C/
-
但是signal的方法設置感覺會對子進程后面再起的孫子進程也生效,但是如果我們在process中調用ansible的模塊,一個playbook會產生很多子進程,這個相較于之前的父進程,現在其實是孫子進程了。ansible里可能會自己定義handler函數,如果用signal方式就會覆蓋ansible的自定義函數,導致孫子進程只能起來一個,后面的步驟就不會執行了。因此如果使用ansible的話推薦使用兩次fork的方式代碼如下:
- import signal from multiprocessing import Process from django.db import connectionsdef close_old_connecttions():connections.close_all()@shared_task def function():signal.signal(signal.SIGCHLD, signal.SIG_IGN)close_old_connections():process = Process(target=_excutor,args=(x,x,x))process.start()process.join() # 這個必須加def _excutor():process = Process(target=_function,args=(x,x,x))process.start()def _function():xxxreturn Ture
-
如果百度過此類問題的不難發現,網上有很多說可以通過fork兩次來避免僵尸進程。其實這是一個很不錯的方法,也是一個比較容易理解的。只是關于該方法的解釋不是很多(可能因為筆者太low,對于很多人來說都是一看就懂的v),在這里我將就該方法做以詳細的解釋和說明,希望對剛接觸此類問題的小伙伴們有所幫助。
首先需要注意的是fork函數是unix/linux系統上特有的,在Windows上運行該函數會直接報錯,而通過都是用Windows機器做開發,在Linux上跑代碼的這種,直接在Linux上寫代碼又是比較麻煩的(如果愿意可以基于VM搭建一個桌面版的CentOS,然后裝個編譯器來開發),所以這里筆者從一開始就選擇Python提供的一種跨平臺的多進程模塊 – multiprocessing來實現多進程(其實multiprocessing中基于Linux的代碼實現邏輯就是fork,對于該模塊源碼初探可詳見傳送門)。
如何理解fork兩次即可達到我們想要的想過呢?此處假設我們的業務場景是父進程一直存在,而子進程的執行周期短,且執行完后就退出。我們知道,當主進程創建一個子進程時,此時子進程的ppid即就是父進程的pid;而子進程結束后如果父進程沒有獲取子進程的退出狀態信息,則子進程會變成僵尸進程;我們又知道,如果一個子進程是孤兒進程的話,那么它就是安全可靠的(不會產生僵尸進程);所以基于以上原因,可以進行如下設計:主進程的業務邏輯保持不變,只是在主進程創建子進程的時候,不直接創建子進程去執行相應的業務邏輯;而是創建一個單獨進程(此處理解為爸爸進程),該進程只干一件事,就是創建原本應該有父進程創建的子進程。即就是將原本的“主進程 => 兒子進程”修改為“主進程 => 爸爸進程 => 兒子進程”,這種設計里只有主進程和兒子進程是需要關注的,而爸爸進程邏輯很簡單,就是初始化兒子進程;所以當爸爸進程結束后兒子進程就淪為孤兒進程了,這樣無論兒子進程執行多久,都不會產生僵尸進程。
有人就會想,爸爸進程退出不也會產生僵尸進程嗎?其實這個問題很好解決,利用上述中的不帶參數的join()方法即可解決。可以在主進程中創建父進程的同時,添加p.join()方法,因為爸爸進程創建兒子進程的耗時很短,所以可以在主進程創建爸爸進程的時候使用p.join()掛起,這個時間差是可以忽略和接受的,這樣當父進程創建完兒子進程后父進程就會立馬結束,此時主進程就會執行p.join()方法獲取到爸爸進程的退出信息,從而徹底消除爸爸進程;這樣進程列表里就只剩下一個主進程和一個而孤兒進程(原本的兒子進程轉化而來);這樣就實現了真正意義上的并發。
總結
以上是生活随笔為你收集整理的接上一篇Ansible和celery的结合,在celery的tasks.py文件里为了实现并发不阻塞的需求,用到了多进程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在python中使用Ansible实现D
- 下一篇: Docker部署配置相关使用总结