一肢簿、進程及狀態(tài)
1. 進程
程序:比如電腦安裝了很多程序,又比如我們編寫一個xxx.py程序深浮,它們靜靜的保存在硬盤中压怠,所以程序是一個靜態(tài)的概念。
進程:一個程序運行起來后飞苇,代碼+用到的資源 稱之為進程菌瘫,它是操作系統(tǒng)分配資源的基本單位。
不僅可以通過線程完成多任務(wù)布卡,進程也是可以的雨让。
2. 進程的狀態(tài)
工作中,任務(wù)數(shù)往往大于cpu的核數(shù)虚汛,即一定有一些任務(wù)正在執(zhí)行,而另外一些任務(wù)在等待cpu進行執(zhí)行,因此導(dǎo)致了有了不同的狀態(tài)。
就緒態(tài):運行的條件都已經(jīng)滿足襟诸,正在等在cpu執(zhí)行
執(zhí)行態(tài):cpu正在執(zhí)行其功能
等待態(tài):等待某些條件滿足陷揪,例如一個程序sleep了耐量,此時就處于等待態(tài)劲藐,紅綠燈汰现,等待消息回復(fù),等待同步鎖 等都是處于等待態(tài)
二妄田、進程的創(chuàng)建-multiprocessing
multiprocessing模塊就是跨平臺版本的多進程模塊俺亮,提供了一個Process類來創(chuàng)建進程對象。
1.示例如下:
from multiprocessing import Process
import time
def run_proc():
"""子進程要執(zhí)行的代碼"""
while True:
print("----2----")
time.sleep(1)
if __name__ == '__main__':
p = Process(target=run_proc)
p.start()
while True:
print("----1----")
time.sleep(1)
創(chuàng)建子進程時疟呐,只需要傳入一個執(zhí)行函數(shù)和函數(shù)的參數(shù)脚曾,創(chuàng)建一個Process實例,用start()方法啟動启具。
#### 2. 進程pid
可以通過os模塊的getpid()方法獲取當(dāng)前進程的id本讥,通過getppid()獲取當(dāng)前進程父進程的id。
示例如下:
import multiprocessing,os,time
def run_proc():
# os.getpid() 取得當(dāng)前進程的id
# os.getppid() :取得當(dāng)前進程的父進程的id
while True:
time.sleep(1)
print("--run_proc--子進程的父進程的id=%d 子進程的id= %d" % (os.getppid(), os.getpid()))
if __name__ == "__main__":
print("--main--主進程的父進程id=%d 主進程的id= %d" % (os.getppid(), os.getpid()))
# 在主進程中創(chuàng)建一個子進程p1 鲁冯,p1的父進程就是主進程
p1 = multiprocessing.Process(target=run_proc)
p1.start()
3. Process語法結(jié)構(gòu)如下:
Process([group [, target [, name [, args [, kwargs]]]]]):
target:如果傳遞了函數(shù)的引用拷沸,可以認(rèn)為這個子進程就執(zhí)行這里的代碼
args:給target指定的函數(shù)傳遞的參數(shù),以元組的方式傳遞
kwargs:給target指定的函數(shù)傳遞命名參數(shù)
name:給進程設(shè)定一個名字晓褪,可以不設(shè)定
group:指定進程組堵漱,大多數(shù)情況下用不到
Process創(chuàng)建的實例對象的常用方法:
start():啟動子進程實例(創(chuàng)建子進程)
is_alive():判斷進程子進程是否還在活著
join([timeout]):是否等待子進程執(zhí)行結(jié)束,或等待多少秒
terminate():不管任務(wù)是否完成涣仿,立即終止子進程
Process創(chuàng)建的實例對象的常用屬性:
name:當(dāng)前進程的別名勤庐,默認(rèn)為Process-N,N為從1開始遞增的整數(shù)
pid:當(dāng)前進程的pid(進程號)
4. 給子進程指定的函數(shù)傳遞參數(shù)
# -*- coding:utf-8 -*-
from multiprocessing import Process
import os,time
def run_proc(name, age, **kwargs):
for i in range(10):
print('子進程運行中好港,name= %s,age=%d ,pid=%d...' % (name, age, os.getpid()))
print(kwargs)
time.sleep(0.2)
if __name__=='__main__':
p = Process(target=run_proc, args=('test',18), kwargs={"m":20})
p.start()
sleep(1) # 1秒中之后愉镰,立即結(jié)束子進程
p.terminate()
p.join()
運行結(jié)果:
子進程運行中,name= test,age=18 ,pid=45097...
{'m': 20}
子進程運行中钧汹,name= test,age=18 ,pid=45097...
{'m': 20}
子進程運行中丈探,name= test,age=18 ,pid=45097...
{'m': 20}
子進程運行中,name= test,age=18 ,pid=45097...
{'m': 20}
子進程運行中拔莱,name= test,age=18 ,pid=45097...
{'m': 20}
5. 進程間不同享全局變量
# -*- coding:utf-8 -*-
from multiprocessing import Process
import os,time
nums = [11, 22]
def work1():
"""子進程要執(zhí)行的代碼"""
print("in process1 pid=%d ,nums=%s" % (os.getpid(), nums))
for i in range(3):
nums.append(i)
time.sleep(1)
print("in process1 pid=%d ,nums=%s" % (os.getpid(), nums))
def work2():
"""子進程要執(zhí)行的代碼"""
print("in process2 pid=%d ,nums=%s" % (os.getpid(), nums))
if __name__ == '__main__':
p1 = Process(target=work1)
p1.start()
p1.join()#阻塞主進程碗降,即等待p1進程執(zhí)行完畢再執(zhí)行后續(xù)的代碼。
p2 = Process(target=work2)
p2.start()
運行結(jié)果:
in process1 pid=11349 ,nums=[11, 22]
in process1 pid=11349 ,nums=[11, 22, 0]
in process1 pid=11349 ,nums=[11, 22, 0, 1]
in process1 pid=11349 ,nums=[11, 22, 0, 1, 2]
in process2 pid=11350 ,nums=[11, 22]
從運行結(jié)果可以看出塘秦,進程間并沒有共享全局變量nums讼渊。
三、進程尊剔、線程對比
1.功能:
進程爪幻,能夠完成多任務(wù),比如運行的QQ再單獨開一個進程接收推送的消息
線程,能夠完成多任務(wù)挨稿,比如運行的QQ開多個線程來發(fā)送消息仇轻、接收文件、視頻聊天等多個任務(wù)
2.定義的不同
進程是操作系統(tǒng)進行資源分配和調(diào)度的一個基本單位.
線程是進程的一個實體,是CPU調(diào)度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統(tǒng)資源,但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源.
3.區(qū)別
一個程序至少有一個進程,一個進程至少有一個線程.
線程的劃分尺度小于進程(資源比進程少)奶甘,使得多線程程序的并發(fā)性高篷店。
進程在執(zhí)行過程中擁有獨立的內(nèi)存單元,而多個線程共享內(nèi)存甩十,從而極大地提高了程序的運行效率
線程不能夠獨立執(zhí)行船庇,必須依存在進程中
4.優(yōu)缺點
線程和進程在使用上各有優(yōu)缺點:線程執(zhí)行開銷小,但不利于資源的管理和保護侣监;而進程正相反。
四臣淤、進程間通信-Queue
Process之間有時需要通信橄霉,操作系統(tǒng)提供了很多機制來實現(xiàn)進程間的通信。
可以使用multiprocessing模塊的Queue實現(xiàn)多進程之間的數(shù)據(jù)傳遞邑蒋,Queue本身是一個消息列隊程序姓蜂,首先用一個小實例來演示一下Queue的工作原理:
#coding=utf-8
from multiprocessing import Queue
q=Queue(3) #初始化一個Queue對象,最多可接收三條put消息
q.put("消息1")
q.put("消息2")
print(q.full()) #False
q.put("消息3")
print(q.full()) #True
#因為消息列隊已滿下面的try都會拋出異常医吊,第一個try會等待2秒后再拋出異常钱慢,第二個Try會立刻拋出異常
try:
q.put("消息4",True,2)
except:
print("消息列隊已滿,現(xiàn)有消息數(shù)量:%s"%q.qsize())
try:
q.put_nowait("消息4")
except:
print("消息列隊已滿卿堂,現(xiàn)有消息數(shù)量:%s"%q.qsize())
#推薦的方式束莫,先判斷消息列隊是否已滿,再寫入
if not q.full():
q.put_nowait("消息4")
#讀取消息時草描,先判斷消息列隊是否為空览绿,再讀取
if not q.empty():
for i in range(q.qsize()):
print(q.get_nowait())
運行結(jié)果:
False
True
消息列隊已滿,現(xiàn)有消息數(shù)量:3
消息列隊已滿穗慕,現(xiàn)有消息數(shù)量:3
消息1
消息2
消息3
說明:
初始化Queue()對象時(例如:q=Queue())饿敲,若括號中沒有指定最大可接收的消息數(shù)量,或數(shù)量為負(fù)值逛绵,那么就代表可接受的消息數(shù)量沒有上限(直到內(nèi)存的盡頭)怀各;
Queue.qsize():返回當(dāng)前隊列包含的消息數(shù)量;
Queue.empty():如果隊列為空术浪,返回True瓢对,反之False ;
Queue.full():如果隊列滿了添吗,返回True,反之False沥曹;
Queue.get([block[, timeout]]):獲取隊列中的一條消息,然后將其從列隊中移除,block默認(rèn)值為True妓美;
1)如果block使用默認(rèn)值僵腺,且沒有設(shè)置timeout(單位秒),消息列隊如果為空壶栋,此時程序?qū)⒈蛔枞ㄍT谧x取狀態(tài))辰如,直到從消息列隊讀到消息為止,如果設(shè)置了timeout贵试,則會等待timeout秒琉兜,若還沒讀取到任何消息,則拋出"Queue.Empty"異常毙玻;
2)如果block值為False豌蟋,消息列隊如果為空,則會立刻拋出"Queue.Empty"異常桑滩;
Queue.get_nowait():相當(dāng)Queue.get(False)梧疲;
Queue.put(item,[block[, timeout]]):將item消息寫入隊列,block默認(rèn)值為True运准;
1)如果block使用默認(rèn)值幌氮,且沒有設(shè)置timeout(單位秒),消息列隊如果已經(jīng)沒有空間可寫入胁澳,此時程序?qū)⒈蛔枞ㄍT趯懭霠顟B(tài))该互,直到從消息列隊騰出空間為止,如果設(shè)置了timeout韭畸,則會等待timeout秒宇智,若還沒空間,則拋出"Queue.Full"異常陆盘;
2)如果block值為False普筹,消息列隊如果沒有空間可寫入,則會立刻拋出"Queue.Full"異常隘马;
Queue.put_nowait(item):相當(dāng)Queue.put(item, False)太防;
我們以Queue為例,在父進程中創(chuàng)建兩個子進程酸员,一個往Queue里寫數(shù)據(jù)蜒车,一個從Queue里讀數(shù)據(jù):
from multiprocessing import Process, Queue
import os, time, random
# 寫數(shù)據(jù)進程執(zhí)行的代碼:
def write(q):
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
# 讀數(shù)據(jù)進程執(zhí)行的代碼:
def read(q):
while True:
if not q.empty():
value = q.get(True)
print('Get %s from queue.' % value)
time.sleep(random.random())
else:
break
if __name__=='__main__':
# 父進程創(chuàng)建Queue,并傳給各個子進程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 啟動子進程pw幔嗦,寫入:
pw.start()
# 等待pw結(jié)束:
pw.join()
# 啟動子進程pr酿愧,讀取:
pr.start()
pr.join()
# pr進程里是死循環(huán),無法等待其結(jié)束邀泉,只能強行終止:
print('')
print('所有數(shù)據(jù)都寫入并且讀完')
五嬉挡、進程池Pool
當(dāng)需要創(chuàng)建的子進程數(shù)量不多時钝鸽,可以直接利用multiprocessing中的Process動態(tài)生成多個進程,但如果是上百甚至上千個目標(biāo)庞钢,手動的去創(chuàng)建進程的工作量巨大拔恰,此時就可以用到multiprocessing模塊提供的Pool方法。
初始化Pool時基括,可以指定一個最大進程數(shù)颜懊,當(dāng)有新的請求提交到Pool中時,如果池還沒有滿风皿,那么就會創(chuàng)建一個新的進程用來執(zhí)行該請求河爹;但如果池中的進程數(shù)已經(jīng)達(dá)到指定的最大值,那么該請求就會等待桐款,直到池中有進程結(jié)束咸这,才會用之前的進程來執(zhí)行新的任務(wù),請看下面的實例:
# -*- coding:utf-8 -*-
from multiprocessing import Pool
import os, time, random
def worker(msg):
t_start = time.time()
print("%s開始執(zhí)行,進程號為%d" % (msg,os.getpid()))
# random.random()隨機生成0~1之間的浮點數(shù)
time.sleep(random.random()*2)
t_stop = time.time()
print(msg,"執(zhí)行完畢魔眨,耗時%0.2f" % (t_stop-t_start))
po = Pool(3) # 定義一個進程池炊苫,最大進程數(shù)3
for i in range(0,10):
# Pool().apply_async(要調(diào)用的目標(biāo),(傳遞給目標(biāo)的參數(shù)元祖,))
# 每次循環(huán)將會用空閑出來的子進程去調(diào)用目標(biāo)
po.apply_async(worker,(i,))
print("----start----")
po.close() # 關(guān)閉進程池,關(guān)閉后po不再接收新的請求
po.join() # 等待po中所有子進程執(zhí)行完成冰沙,必須放在close語句之后
print("-----end-----")
運行結(jié)果:
----start----
0開始執(zhí)行,進程號為21466
1開始執(zhí)行,進程號為21468
2開始執(zhí)行,進程號為21467
0 執(zhí)行完畢,耗時1.01
3開始執(zhí)行,進程號為21466
2 執(zhí)行完畢执虹,耗時1.24
4開始執(zhí)行,進程號為21467
3 執(zhí)行完畢拓挥,耗時0.56
5開始執(zhí)行,進程號為21466
1 執(zhí)行完畢,耗時1.68
6開始執(zhí)行,進程號為21468
4 執(zhí)行完畢袋励,耗時0.67
7開始執(zhí)行,進程號為21467
5 執(zhí)行完畢侥啤,耗時0.83
8開始執(zhí)行,進程號為21466
6 執(zhí)行完畢,耗時0.75
9開始執(zhí)行,進程號為21468
7 執(zhí)行完畢茬故,耗時1.03
8 執(zhí)行完畢盖灸,耗時1.05
9 執(zhí)行完畢,耗時1.69
-----end-----
multiprocessing.Pool常用函數(shù)解析:
apply_async(func[, args[, kwds]]) :使用非阻塞方式調(diào)用func(并行執(zhí)行磺芭,堵塞方式必須等待上一個進程退出才能執(zhí)行下一個進程)赁炎,args為傳遞給func的參數(shù)列表,kwds為傳遞給func的關(guān)鍵字參數(shù)列表钾腺;
close():關(guān)閉Pool徙垫,使其不再接受新的任務(wù);
terminate():不管任務(wù)是否完成放棒,立即終止姻报;
join():主進程阻塞,等待子進程的退出间螟, 必須在close或terminate之后使用吴旋;
進程池中的Queue
如果要使用Pool創(chuàng)建進程损肛,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue()荣瑟,否則會得到一條如下的錯誤信息:
RuntimeError: Queue objects should only be shared between processes through inheritance.
下面的實例演示了進程池中的進程如何通信:
# -*- coding:utf-8 -*-
# 修改import中的Queue為Manager
from multiprocessing import Manager,Pool
import os,time,random
def reader(q):
print("reader啟動(%s),父進程為(%s)" % (os.getpid(), os.getppid()))
for i in range(q.qsize()):
print("reader從Queue獲取到消息:%s" % q.get())
def writer(q):
print("writer啟動(%s),父進程為(%s)" % (os.getpid(), os.getppid()))
for i in "itcast":
q.put(i)
if __name__=="__main__":
print("(%s) start" % os.getpid())
q = Manager().Queue() # 使用Manager中的Queue
po = Pool()
po.apply_async(writer, (q,))
time.sleep(1) # 先讓上面的任務(wù)向Queue存入數(shù)據(jù)治拿,然后再讓下面的任務(wù)開始從中取數(shù)據(jù)
po.apply_async(reader, (q,))
po.close()
po.join()
print("(%s) End" % os.getpid())
運行結(jié)果:
(11095) start
writer啟動(11097),父進程為(11095)
reader啟動(11098),父進程為(11095)
reader從Queue獲取到消息:i
reader從Queue獲取到消息:t
reader從Queue獲取到消息:c
reader從Queue獲取到消息:a
reader從Queue獲取到消息:s
reader從Queue獲取到消息:t
(11095) End