多任務原理
什麼叫多任務
? ? ?
- 現代操作系統(Windows. Mac OS、Linux、 UNIX等)都支持
多任務
- 操作系統同時可以運行多個任務
單核CPU實現多任務原理
操作系統輪流讓各個任務交替執行,QQ執行2us,切換到微信,在執行2us, 再切換到陌陌,執行.2…表面是看,每個任務反覆執行下去
但是CPU調度執行速度太快了,導致我們感覺就像所有任務都在同時執行一樣
多核CPU實現多任務原理
真正的並性執行多任務,只能在多核CPU上實現,但是由於任務數量遠遠多於CPU的核心數量,所以,操作系統也會自動把很多任務輪流調度到每個核心上執行
併發
:看上去一起執行,任務書數多於CPU核心數並行
:真正起執行,任務數小於等於CPU核心數
實現多任務
的方式:
- 多進程模式
- 多線程模式
- 協程模式
- 多進程+多線程模式
進程
對於操作系統而言,一個任務就是一個進程。
進程是系統中程序執行和資源分配的基本單位,每個進程都有屬於自己的數據段、代碼段、堆棧段,互不干擾,互不影響
單任務現象
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("父進程結束")
運行結果:
如果有錯誤或者有疑問,請私信我喔,感謝觀看