進(jìn)程與線程的使用在代碼運(yùn)行效率的優(yōu)化上是一個(gè)不可或缺的技能,看完本篇文章,你將對(duì)進(jìn)程與線程有一定的了解。
1. 多任務(wù)深入理解
1.1. CPU時(shí)間片
在單核中,并行基本上是并發(fā),只有多核才能真正實(shí)現(xiàn)并行。
1.2. 進(jìn)程的概念
每個(gè)進(jìn)程擁有自己的獨(dú)立的地址空間,內(nèi)存,數(shù)據(jù)棧,以及其它用于跟蹤執(zhí)行的輔助數(shù)據(jù)。
各個(gè)進(jìn)程之間相互獨(dú)立,互不影響。
2. 多進(jìn)程實(shí)現(xiàn)
2.1. 單一進(jìn)程
使用睡眠來(lái)模擬耗時(shí)操作,查看整個(gè)程序的運(yùn)行時(shí)間。
'''單進(jìn)程'''
import time
def new_time():
"""格式化時(shí)間"""
# print(time.time())
# outputs:1638166819.3935854,時(shí)間戳,是從1970年1月1日開(kāi)始所經(jīng)過(guò)的秒數(shù)
# print(time.localtime(time.time()))
# outputs:time.struct_time(tm_year=2021, tm_mon=11, tm_mday=29, tm_hour=14, tm_min=20, tm_sec=19, tm_wday=0, tm_yday=333, tm_isdst=0),將時(shí)間戳轉(zhuǎn)換成年月日時(shí)分秒
# print(time.asctime(time.localtime(time.time())))
# outputs:Mon Nov 29 14:20:19 2021
return time.asctime(time.localtime(time.time()))
def func():
print('inner-start:',new_time())
time.sleep(5) # 休眠,模擬io耗時(shí)操作
print('inner-end:',new_time())
print('outer-start:',new_time())
time.sleep(5)
func()
print('outer-end:',new_time())
運(yùn)行結(jié)果
2.2. 開(kāi)啟多進(jìn)程
使用多進(jìn)程來(lái)分擔(dān)耗時(shí)任務(wù),在另一個(gè)進(jìn)程中運(yùn)行耗時(shí)任務(wù),這樣主進(jìn)程就不會(huì)受到影響。當(dāng)子進(jìn)程執(zhí)行完時(shí),返回子進(jìn)程的運(yùn)行結(jié)果。
'''開(kāi)啟多進(jìn)程不包含參數(shù)'''
import multiprocessing # 導(dǎo)入多進(jìn)程模塊
import time
def new_time():
"""格式化時(shí)間"""
# print(time.time())
# outputs:1638166819.3935854,時(shí)間戳,是從1970年1月1日開(kāi)始所經(jīng)過(guò)的秒數(shù)
# print(time.localtime(time.time()))
# outputs:time.struct_time(tm_year=2021, tm_mon=11, tm_mday=29, tm_hour=14, tm_min=20, tm_sec=19, tm_wday=0, tm_yday=333, tm_isdst=0),將時(shí)間戳轉(zhuǎn)換成年月日時(shí)分秒
# print(time.asctime(time.localtime(time.time())))
# outputs:Mon Nov 29 14:20:19 2021
return time.asctime(time.localtime(time.time()))
def func():
print('inner-start:',new_time())
time.sleep(5) # 休眠,模擬io耗時(shí)操作
print('inner-end:',new_time())
print('outer-start:',new_time())
p1 = multiprocessing.Process(target=func) # 實(shí)例化一個(gè)進(jìn)程對(duì)象,target告訴它做什么事
p1.start() # 開(kāi)啟子進(jìn)程,能夠讓任務(wù)更快地做完
time.sleep(5)
print('outer-end:',new_time())
運(yùn)行結(jié)果
可以發(fā)現(xiàn)做同樣的工作,運(yùn)行多進(jìn)程程序比運(yùn)行單進(jìn)程程序快了5秒鐘。
'''開(kāi)啟多進(jìn)程包含參數(shù)'''
import multiprocessing # 導(dǎo)入多進(jìn)程模塊
import time
def new_time():
"""格式化時(shí)間"""
# print(time.time())
# outputs:1638166819.3935854,時(shí)間戳,是從1970年1月1日開(kāi)始所經(jīng)過(guò)的秒數(shù)
# print(time.localtime(time.time()))
# outputs:time.struct_time(tm_year=2021, tm_mon=11, tm_mday=29, tm_hour=14, tm_min=20, tm_sec=19, tm_wday=0, tm_yday=333, tm_isdst=0),將時(shí)間戳轉(zhuǎn)換成年月日時(shí)分秒
# print(time.asctime(time.localtime(time.time())))
# outputs:Mon Nov 29 14:20:19 2021
return time.asctime(time.localtime(time.time()))
def func(x,y):
print('inner-start:',new_time())
print(x*y)
time.sleep(5) # 休眠,模擬io耗時(shí)操作
print('inner-end:',new_time())
print('outer-start:',new_time())
p1 = multiprocessing.Process(target=func,args=(3,4)) # 實(shí)例化一個(gè)進(jìn)程對(duì)象,target告訴它做什么事,必須得傳元組,所以單個(gè)參數(shù)時(shí)需要(3,)
p1.start() # 開(kāi)啟子進(jìn)程,能夠讓任務(wù)更快地做完
time.sleep(5)
print('outer-end:',new_time())
運(yùn)行結(jié)果
2.3. 多進(jìn)程
首先是multiprocessing.Process實(shí)例化,并且指定回調(diào)函數(shù),參數(shù)列表。
實(shí)例化之后可以直接調(diào)用運(yùn)行,這就實(shí)現(xiàn)了多進(jìn)程運(yùn)行,節(jié)省運(yùn)行時(shí)間。
這里并行都只是Python層面的,并不是實(shí)際層面的。
當(dāng)總進(jìn)程數(shù)多于核心數(shù)的時(shí)候,多余的進(jìn)程就沒(méi)有了效果。
多進(jìn)程是由操作系統(tǒng)調(diào)度運(yùn)行。
代碼實(shí)現(xiàn)過(guò)程
3. 多線程實(shí)現(xiàn)
3.1. 輕量級(jí)進(jìn)程
如果把進(jìn)程比做一個(gè)工廠,那么線程就是工廠里面的工人,也就是一個(gè)進(jìn)程可以包含多個(gè)線程。
車(chē)間的空間是工人們共享的,比如許多房間是每個(gè)工人都可以進(jìn)出的。這象征一個(gè)進(jìn)程的內(nèi)容空間是共享的,每個(gè)線程都可以使用這些共享內(nèi)存。
一個(gè)線程可以被搶占(中斷)也可以臨時(shí)掛起(睡眠)等。
線程的調(diào)度由Python解釋器調(diào)度,而進(jìn)程由操作系統(tǒng)調(diào)度。
3.2. 多線程
多線程不適合解決計(jì)算密集型的情形,但是適合IO密集型的場(chǎng)景。因?yàn)樵谕粋€(gè)進(jìn)程內(nèi),并不會(huì)額外分配其它的系統(tǒng)資源。
Python中的多線程在遇到阻塞時(shí),會(huì)自動(dòng)切換到非阻塞的線程上去。
'''多線程實(shí)現(xiàn)'''
import time
import multiprocessing # 導(dǎo)入多進(jìn)程模塊
import threading # 導(dǎo)入多線程模塊
def new_time():
"""格式化時(shí)間"""
# print(time.time())
# outputs:1638166819.3935854,時(shí)間戳,是從1970年1月1日開(kāi)始所經(jīng)過(guò)的秒數(shù)
# print(time.localtime(time.time()))
# outputs:time.struct_time(tm_year=2021, tm_mon=11, tm_mday=29, tm_hour=14, tm_min=20, tm_sec=19, tm_wday=0, tm_yday=333, tm_isdst=0),將時(shí)間戳轉(zhuǎn)換成年月日時(shí)分秒
# print(time.asctime(time.localtime(time.time())))
# outputs:Mon Nov 29 14:20:19 2021
return time.asctime(time.localtime(time.time()))
def func(x,y):
print('inner-start:',new_time())
print(x*y)
time.sleep(5) # 休眠,模擬io耗時(shí)操作
print('inner-end:',new_time())
print('outer-start:',new_time())
t1 = threading.Thread(target=func,args=(3,4)) # 實(shí)例化一個(gè)線程對(duì)象
t1.start() # 開(kāi)啟子線程
time.sleep(5)
print('outer-end:',new_time())
運(yùn)行結(jié)果
3.3. GIL全局解釋鎖
Python發(fā)明之初,還沒(méi)有多核CPU的概念。為了利用多核,Python開(kāi)始支持多線程,而解決多線程之間數(shù)據(jù)完整性和狀態(tài)同步的最簡(jiǎn)單方法自然就是加鎖,于是就有了GIL這把全局解釋鎖。
GIL鎖要求,任何進(jìn)程中,一次只能有一個(gè)線程在執(zhí)行,因此,并不能為多個(gè)線程分配多個(gè)CPU,所以Python中的線程只能實(shí)現(xiàn)并發(fā),而不能實(shí)現(xiàn)并行。
4. 并發(fā)服務(wù)器
4.1. 多進(jìn)程實(shí)現(xiàn)并發(fā)服務(wù)器
'''多進(jìn)程實(shí)現(xiàn)并發(fā)服務(wù)器'''
import time
import socket
import multiprocessing
server = socket.socket()
server.bind(('127.0.0.1',8989))
server.listen(10)
def handle(conn):
while True:
recv_data = conn.recv(1024)
if recv_data:
print(recv_data)
conn.send(recv_data)
else:
conn.close()
break
while True:
conn,address = server.accept() # 生成套接字
p1 = multiprocessing.Process(target=handle,args=(conn,))
p1.start()
4.2. 多線程實(shí)現(xiàn)并發(fā)服務(wù)器
'''多線程實(shí)現(xiàn)并發(fā)服務(wù)器'''
import time
import socket
import threading
server = socket.socket()
server.bind(('127.0.0.1',8989))
server.listen(10)
def handle(conn):
while True:
recv_data = conn.recv(1024)
if recv_data:
print(recv_data)
conn.send(recv_data)
else:
conn.close()
break
while True:
conn,address = server.accept() # 生成套接字
t1 = threading.Thread(target=handle,args=(conn,))
t1.start()
4.3. 進(jìn)程與線程認(rèn)識(shí)客戶端
import socket
client = socket.socket() # 創(chuàng)建一個(gè)socket對(duì)象,命名為客戶端
client.connect(('127.0.0.1',8989))# 連接服務(wù)器端口,注意這里填入的是元組
for i in range(1000):
client.send(b'hello') # 發(fā)送hello給服務(wù)器
print(client.recv(1024))
文章到這里就結(jié)束了!希望大家能多多支持Python(系列)!六個(gè)月帶大家學(xué)會(huì)Python,私聊我,可以問(wèn)關(guān)于本文章的問(wèn)題!以后每天都會(huì)發(fā)布新的文章,喜歡的點(diǎn)點(diǎn)關(guān)注!一個(gè)陪伴你學(xué)習(xí)Python的新青年!不管多忙都會(huì)更新下去,一起加油!
Editor:Lonelyroots




