Python(二十九)進(jìn)程與線程

進(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容