Python中進程與線程的(詳細)教程之進程

多任務原理

什麼叫多任務? ? ?

  • 現代操作系統(Windows. Mac OS、Linux、 UNIX等)都支持多任務
  • 操作系統同時可以運行多個任務
單核CPU實現多任務原理

操作系統輪流讓各個任務交替執行,QQ執行2us,切換到微信,在執行2us, 再切換到陌陌,執行.2…表面是看,每個任務反覆執行下去
但是CPU調度執行速度太快了,導致我們感覺就像所有任務都在同時執行一樣

多核CPU實現多任務原理

真正的並性執行多任務,只能在多核CPU上實現,但是由於任務數量遠遠多於CPU的核心數量,所以,操作系統也會自動把很多任務輪流調度到每個核心上執行

  • 併發:看上去一起執行,任務書數多於CPU核心數
  • 並行:真正起執行,任務數小於等於CPU核心數

實現多任務的方式:

  1. 多進程模式
  2. 多線程模式
  3. 協程模式
  4. 多進程+多線程模式

進程

對於操作系統而言,一個任務就是一個進程。

進程是系統中程序執行和資源分配的基本單位,每個進程都有屬於自己的數據段、代碼段、堆棧段,互不干擾,互不影響

單任務現象

import time

def run():
    while True:
        print("aaa")
        time.sleep(1.2)

if __name__ == "__main__":
    while True:
        print("jack")
        time.sleep(1)

    # 永遠也不會執行到run方法,只有上面的while循環結束纔可以執行。
    # 這是單任務現象。

    run()

運行結果:
在這裏插入圖片描述

永遠也執行不到上面的run方法中的aaa,這就是單線程,只能等一個執行完了再執行另一個

啓動進程實現多任務

import os
from multiprocessing import Process
import time


# 子進程需要執行的代碼
def run(value):
    while True:
    
        # os.getpid()獲取當前進程的id號
        # os.getppid()獲取當前進程的父進程的id號
        print("aaa----%s----%s----%s" % (value, os.getpid(), os.getppid()))
        time.sleep(1.2)

if __name__ == "__main__":
    print("主(父)進程啓動 %s" % (os.getpid()))
    
    # 創建子進程
    # target說明進程執行的任務
    # args可以傳參,因爲是元組,所以只傳一個參數要加逗號
    p = Process(target=run, args=("ok",))
    # 啓動進程
    p.start()
    
    while True:
        print("jack")
        time.sleep(1)

運行結果:
在這裏插入圖片描述

可以很明顯的看到,兩個是同時進行的,因爲停頓的秒數不同,但確確實實是兩個while True在運行,很神奇,另外(target=run),方法是需要加括號的,只需要傳入方法名就行

父進程和子進程的先後順序

from multiprocessing import Process
import time

def run():
    print("子進程開始")
    time.sleep(2)
    print("子進程結束")

def eat():
    print("開始吃")
    time.sleep(1)
    print("吃完了")


if __name__ == "__main__":
    print("父進程開始")
    p = Process(target=run)
    p.start()

    p1 = Process(target=eat)
    p1.start()

    # 可以發現,父進程根本也沒有等子進程執行完在結束,而是各執行各的
    time.sleep(1)
    print("父進程結束")

運行結果:
在這裏插入圖片描述

可以發現,父進程根本也沒有等子進程執行完在結束,而是各執行各的,但是這並不符合常規
正常的應該是老闆父進程)指揮員工子進程)做事

讓父進程等待子進程執行完之後再結束
from multiprocessing import Process
import time

def run():
    print("子進程開始")
    time.sleep(2)
    print("子進程結束")

def eat():
    print("開始吃")
    time.sleep(1)
    print("吃完了")


if __name__ == "__main__":
    print("父進程開始")
    p = Process(target=run)
    p.start()

    p1 = Process(target=eat)
    p1.start()
    
	# 要想讓父進程等待子進程執行完在結束,在下面加個.join()
    p.join()
    p1.join()
  
    time.sleep(1)
    print("父進程結束")

運行結果:
在這裏插入圖片描述

啓動大量子進程

查看電腦核心數的方法
1、打開任務管理器
在這裏插入圖片描述
2、點擊性能
在這裏插入圖片描述
3、查看核心數,上面顯示的是6核心12個邏輯處理器,說明我們可以同時進行12個任務
在這裏插入圖片描述
例子:

import os
import random
import time
from multiprocessing import Pool


def run(name):
    print("子進程%d啓動--%s" % (name, os.getpid()))
    start = time.time()
    time.sleep(random.choice([1, 2, 3]))
    end = time.time()
    print("子進程%d結束--%s--耗時%.2f" % (name, os.getpid(), end - start))

if __name__ == "__main__":
    print("父進程啓動")
    # 創建多個進程
    # 進程池
    # Pool(n)  n表示可以同時執行的進程數量,不寫的話,默認大小爲CPU核心數
    pool = Pool(4)
    for i in range(1, 6):
        # 創建進程,放入進程池統一管理
        pool.apply_async(run, args=(i,))

    # 在調用join之前必須先調用close,調用close之後就不能再繼續添加新的進程了
    pool.close()
    pool.join()
    print("父進程結束")

運行結果:
在這裏插入圖片描述

請看仔細一點,在進程2結束的瞬間,進程5啓動,而且他們用的是同一個進程號,是這個進程工作完立馬去工作下一個!
筆者因爲偷懶,所以用循環處理一種方法,如果要進行不同的進程
例子:

# 我們可以這樣
if __name__ == "__main__":
    print("父進程啓動")
    # 因爲要執行的進程大於我們池子的容量,才看得出效果
    pool = Pool(2)
    # 定義不同的進程
	pool.apply_async(run1)
    pool.apply_async(run2)
    pool.apply_async(run3)
    pool.close()
    pool.join()
    print("父進程結束")

因爲筆者比較偷懶,故沒有作演示,請見諒

全局變量在多個進程中不能共享

from multiprocessing import Process
import time

num = 100

def run():
    print("子進程開始")
    # 若想在函數內部對函數外的變量進行操作,就需要在函數內部聲明其爲global
    global num
    num += 1
    time.sleep(2)
    print("子進程結束")

def eat():
    print("開始吃")
    time.sleep(3)
    print("吃完了")

if __name__ == "__main__":
    print("父進程開始")
    print(num)
    p = Process(target=run)
    p.start()
    p.join()
    # 在子進程中修改全局變量對父進程中的全局變了沒有影響
    # 在創建子進程時對全局變量做了一個備份,父進程中的與子進程中的num是兩個完全不同的兩個變量
    # 兩個子進程中也不能共享
    print("父進程結束")

運行結果:
在這裏插入圖片描述

可以看到,num數並沒有改變,所以每個子進程都是單獨工作的,全局變量在多個進程中不能共享

單進程拷貝文件

文件是15個空白的文本文檔

import os
import time

def copy(readPath, writePath):
    # 讀取兩個文件
    fr = open(readPath, "rb")
    fw = open(writePath, "wb")
    # 讀並寫入
    context = fr.read()
    fw.write(context)
    # 關閉
    fr.close()
    fw.close()

path = r"D:\新建文件夾\copy"
topath = r"D:\新建文件夾\tocopy"

# 讀取path下的所有的文件
fileList = os.listdir(path)

# 啓動for循環處理每一個文件
start = time.time()
for fileName in fileList:
    copy(os.path.join(path, fileName), os.path.join(topath, fileName))
end = time.time()
print("總耗時:%.5f" % (end - start))

運行結果:

總耗時:0.00400

多進程拷貝文件

import os
import time
from multiprocessing import Pool

def copy(readPath, writePath):
    # 讀取兩個文件
    fr = open(readPath, "rb")
    fw = open(writePath, "wb")
    # 讀並寫入
    context = fr.read()
    fw.write(context)
    # 關閉
    fr.close()
    fw.close()

path = r"D:\新建文件夾\copy"
topath = r"D:\新建文件夾\tocopy"

if __name__ == "__main__":
    # 讀取path下的所有的文件
    fileList = os.listdir(path)
    start = time.time()
    pool = Pool(6)
    for fileName in fileList:
        pool.apply_async(copy, args=(os.path.join(path, fileName), os.path.join(topath, fileName)))
    pool.close()
    pool.join()
    end = time.time()
    print("總耗時:%.5f" % (end - start))

運行結果:

總耗時:0.19804

對比一下,是不是很驚訝,爲什麼多進程比單進程慢呢?

  • 極少的數據,多進程會比單進程,只有的數據才能體現出優勢
  • 但是要是100個進程一起工作,理論上也不會快很多,因爲因爲多進程啓動也費時間和資源,銷燬也費時間與資源

我們現在測試一下2G,27個資源

  • 單進程:總耗時:19.93352
  • 多進程:總耗時:15.03159

總而言之,多進程理論上是比單進程快些

封裝進程對象

我們先創建進程類
名字叫jackProcess.py

import time
from multiprocessing import Process
import os

# 繼承自Process
class JackProcess(Process):
    def __init__(self, name):
        Process.__init__(self)
        self.name = name

    def run(self):
        print("子進程(%s--%s)啓動" % (self.name, os.getpid()))
        time.sleep(2)
        print("子進程(%s--%s)結束" % (self.name, os.getpid()))

然後再把類實例化
創建一個新文件

from jackProcess import JackProcess


if __name__ == "__main__":
    print("父進程啓動")
	# 創建子進程
    pool = JackProcess("test")
    # 自動調用pool進程對象的run方法
    pool.start()
    pool.join()
    print("父進程結束")

運行結果:
在這裏插入圖片描述

不知道大家有沒有注意,我們這裏並沒有調用run方法,然後也可以執行,這是爲什麼呢?
因爲我們使用pool.start的時候,它會自動調用類裏面的方法,很方便,如果有100個方法需要我們去調用,那把進程封裝進對象,這樣無疑是最方便最快速的

進程間通信

進程中的子進程是不能共享數據的,但是能互相拿數據和給數據
multiprocessing中的Queue
相當於把子進程A的東西傳到隊列中去,然後子進程B再從隊列中拿數據

from multiprocessing import Process, Queue
import os
import time

def write(queue):
    print("啓動寫子進程--%s" % (os.getpid()))
    for i in range(1,5):
    	# 把數據給隊列
        queue.put(i)
        time.sleep(1)
    print("結束寫子進程--%s" % (os.getpid()))


def read(queue):
    print("啓動讀子進程--%s" % (os.getpid()))
    while True:
    	# 從隊列裏面拿數據,True是一直拿
        value = queue.get(True)
        print("value = " + str(value))
    print("結束讀子進程--%s" % (os.getpid()))

if __name__ == "__main__":
    print("父進程開始")
    # 父進程創建隊列,並傳遞給子進程
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))

    # 雖然這兩個先是pw在前,但是不一定執行的時候是pw在前
    pw.start()
    pr.start()

    pw.join()
    print("父進程結束")

爲什麼拿要用死循環呢?因爲read方法如果從隊列裏面拿的話,根本不知道什麼時候拿,拿什麼,所以我們要一直拿
運行結果:
在這裏插入圖片描述

有發現,我們的程序一直都在運行,這是爲什麼呢?因爲我們的read方法是死循環,根本結束不了,所以我們要強制性結束

	pw.join()
    # pr進程裏面是個死循環,無法等待其結束,只能強行結束
    pr.terminate()
    print("父進程結束")

運行結果:
在這裏插入圖片描述
如果有錯誤或者有疑問,請私信我喔,感謝觀看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章