一篇文章帶你解析Python進程
前言
進程,一個新鮮的字眼,可能有些人并不了解,它是系統某個運行程序的載體,這個程序可以有單個或者多個進程,一般來說,進程是通過系統CPU 內核數來分配并設置的,我們可以來看下系統中的進程:
可以看到,360瀏覽器是真的皮,這么多進程啊,當然可以這樣來十分清楚的看進程線程使用情況:
通過任務管理器中的資源監視器,是不是很厲害了,哈哈哈。講完了這些,再說說用法。
1.基本用法
進程能干什么,這是我們要深思熟慮的事情。我們都知道一個程序運行會創建進程,所以程序在創建這些進程的時候,為了讓它們更能有條不紊的工作,肯定也加入了線程。
那么一條進程里面就會有多個線程協同作戰,但是進程不可以創建過多,不然會消耗資源,除非你開發的是一個大型的系統。那么,我們現在就來創建一個進程吧。
一、創建進程
1.在創建進程之前,我們先導入進程的模塊,代碼如下:
- import multiprocess as m
- m.Process(target,args)
其實這種寫法是不對的,就好比bs4中的BeautifulSoup,你想通過先導入bs4,然后再引入BeautifulSoup是行不通的,必須這樣:
- from multiprocessing import Process
- Process(group, target, args, kwargs, name)
- group:用戶組
- target:調用函數
- args:參數元祖
- kwargs:參數字典
- name:子進程名稱
可以看出進程和線程的用法基本差不多,只是名稱功能不同而已。而且還有很多其它優秀的方法:
- # 返回當前進程存活的子進程的列表。調用該方法有“等待”已經結束的進程的副作用。
- multiprocessing.active_children()
- # 返回系統的CPU數量。
- multiprocessing.cpu_count()
2.創建單個進程
由上述參數可知函數的返回值,基本與線程無差異化。
- #啟動進程,調用進程中的run()方法。
- start()
- #進程活動的方法
- run()
- #強制終止進程,不會進行任何清理操作。如果終止前創建了子進程,那么該子進程在其強制結束后變為僵尸進程;如果該進程還保存了一個鎖,那么也將不會被釋放,進而導致死鎖。
- terminate()
- #判斷某進程是否存活,存活返回True,否則False。
- is_alive()
- 主線程等待子線程終止。timeout為可選擇超時時間;需要強調的是:p.join只能join住start開啟的進程,而不能join住run開啟的進程。
- join([timeout])
- #設置進程為后臺守護進程;當該進程的父進程終止時,該進程也隨之終止,并且該進程不能創建子進程,設置該屬性必須在start()之前
- daemon
- #進程名稱。
- name
- #進程pid,在start后才能產生
- pid
- #子進程的退出代碼。如果進程尚未終止,這將是 None,負值-N表示子進程被信號N終止。
- exitcode
- #進程身份驗證,默認是os.urandom()隨機生成的字符串。校驗網進程連接是否正確
- authkey
- #系統對象的數字句柄,當進程結束時將變為 "ready" 。
- sentinel
- #殺進程
- kill()
- #關閉進程
- close()
請注意:創建進程務必將它加入如下語句中:
- if __name__ == '__main__':
這樣就實現了我們的一個關于進程的程序了。另外我們也可以通過繼承進程類來實現:
可以說我們每創建一個進程它就會有一個ID來標志它,下面情況:
3.創建多個進程
單個進程往往都是不夠用的,所有我們需要創建一個多進程,多進程創建方法也很簡單,加一層循環即可:
這樣就輕松創建了多進程的任務,速度比以往就要更快了。
4.進程池
進程池的設計之初就是為了方便我們更有效的利用資源,避免浪費,如果任務量大就多個核一起幫忙,如果少就只開一兩個核,下面我們來看看實現過程:
首先導入包:
- from multiprocessing import Pool
- import multiprocessing as m
進程池的安裝包為Pool,然后我們來看下它的CPU內核數:
- num=m.cpu_count()#CPU內核數
緊接著我們在來創建進程池:
- pool=multiprocessing.Pool(num)
進程池中也有很多方法供我們使用:
- apply(func,args,kwargs) 同步執行(串行) 阻塞
- apply_async(func,args,kwargs) 異步執行(并行) 非阻塞
- terminate() 強制終止進程,不在處理未完成的任務。
- join() 主進程阻塞,等待子進程的退出。必須在close或terminate()之后使用
- close() 等待所有進程結束后,才關閉進程池
- map(func,iterable,chunksize=int) map函數的并行版本,保持阻塞直到獲得結果
- #返回一個可用于獲取結果的對象,回調函數應該立即執行完成,否則會阻塞負責處理結果的線程
- map_async(func,iterable,chunksize,callback,error_callback)
- imap(func,iterable,chunksize) map的延遲執行版本
- #和imap() 相同,只不過通過迭代器返回的結果是任意的
- imap_unordered(func,iterable,chunksize)
- #和 map() 類似,不過 iterable 中的每一項會被解包再作為函數參數。
- starmap(func,iterable,chunksize)
為此我們可以創建同步和異步的程序,如果你對這對于爬蟲來說是很不錯的選擇,小點的爬蟲同步就好,大的爬蟲異步效果更佳,很多人不了解異步和同步,其實同步異步就是串行和并行的意思串行和并行簡單點說就是串聯和并聯。下面我們通過實例一起來看一下:
一、串行
二、并行
可以看到,僅僅只是一個參數的變化而已,其它的都是大同小異,我們獲取到了當前進程的pid,然后把它打印出來了。
5.鎖
雖然異步編程多進程給我們帶來了便利,但是進程啟動后是不可控的,我們需要將它控制住,讓它干我們覺得有意義的事,這個時候我們需要給它加鎖,和線程一樣都是lock:
首先導入進程鎖的模塊:
- from multiprocessing import Lock
然后我們來創建一個關于鎖的程序:
可以看到,加鎖的過程還是比較順利的,跟多線程一樣簡單,但是相對來說速度會慢一點。既然有Lock,那么勢必就有RLock了,在python 中,進程和線程的很多用法一致,鎖就是。我們可以把它改為RLock,下面便是可重入鎖,也就是可以遞歸:
- import time
- lock1=RLock()
- lock2=RLock()
- s=time.time()
- def jc(num):
- lock1.acquire()
- lock2.acquire()
- print('start')
- print(m.current_process().pid,'run----',str(num))
- lock1.release()
- lock2.release()
- print('end')
- if __name__ == '__main__':
- aa=[]
- for y in range(12):
- pp=Process(target=jc,args=(y,))
- pp.start()
- aa.append(pp)
- for x in aa:
- x.join()
- e=time.time()
- print(e-s)
6.進程間通信
一、Event
進程間用于通信,方法和線程的一模一樣,這里舉個小栗子,不在詳細描述,不懂的可以看我上一篇關于線程的文章,我們今天要講的是其它的進程間通信方式,下面請看:
- import time
- e=Event()
- def main(num):
- while True:
- if num<5:
- e.clear() #清空信號標志
- print('清空')
- if num>=5:
- e.wait(timeout=1) #等待信號標志為真
- e.set()
- print('啟動')
- if num==10:
- e.wait(timeout=3)
- e.clear()
- print('退出')
- break
- num+=1
- time.sleep(2)
- if __name__ == '__main__':
- for y in range(10):
- pp=Process(target=main,args=(y,))
- pp.start()
- pp.join()
二、管道傳遞消息
管道模塊初始化后返回兩個參數,一個為發送者,一個為接收者,它有個參數可以設置模式為全雙工或者半雙工,全雙工收發一體,半雙工只收或者只發,先了解下它的方法:
- p1,p2=m.Pipe(duplex=bool) #設置是否全雙工,返回兩個連接對象
- p1.send() #發送
- p2.recv() #接收
- p1.close() #關閉連接
- p1.fileno() #返回連接使用的整數文件描述符
- p1.poll([timeout]) #如果連接上的數據可用,返回True,timeout指定等待的最長時限。
- p2.recv_bytes([maxlength]) #接收最大字節數
- p1.send_bytes([maxlength]) #發送最大字節數
- #接收一條完整的字節消息,并把它保存在buffer對象中,offset指定緩沖區中放置消息處的字節位移.
- p2.recv_bytes_into(buffer [, offset])
先收后發,其實我們完全可以使用鎖來控制它的首發,可以讓它一邊收一邊發。
三、隊列
隊列與其它不同的是它采取插入和刪除的方法,讓我們來看下:
- def fd(a):
- for y in range(10):
- a.put(y) #插入數據
- print('插入:',str(y))
- def df(b):
- while True:
- aa=b.get(True) #刪除數據
- print('釋放:',str(aa))
- if __name__ == '__main__':
- q=Queue()
- ff=Process(target=fd,args=(q,))
- dd=Process(target=df,args=(q,))
- ff.start() #開始運行
- dd.start()
- dd.terminate() #關閉
- ff.join()
以上講的隊列主要用于多進程的隊列,還有一個進程池的隊列,它在Manager模塊中。
7.信號量
與線程中完全一樣,這里不在贅述,看下例:
- s=Semaphore(3)
- s.acquire()
- print(s.get_value())
- s.release()
- print(s.get_value())
- print(s.get_value())
- s.release()
- print(s.get_value())
- s.release()
- output:
- 2
- 3
- 3
- 4
8.數據共享
共享數據類型可以直接通過進程模塊來設置:
- 數值型:m.Value()
- 數組性:m.Array()
- 字典型:m.dict()
- 列表型:m.list()
也可以通過進程的Manager模塊來實現:
- Manager().dict()
- Manager.list()
下面我們就來舉例說明下吧:
可以看到我們成功的將數據添加了進去,形成了數據的共享。
2.總結
通過對進程的描述,相信大家對進程此刻有了個深刻的感悟了吧,突然想起個事,就是大家學習時可能查資料會在網上搜索,那么我建議你專心看好我這篇好了,因為據我所知,那些都是錯的,而且更讓我納悶的是,明明代碼是錯的,放出來的執行效果卻是對的,這讓我百思不得其解,哈哈哈。