文章目錄
進程
多任務
程序的運行都是cpu和內存協同工作的結果
常用的操作系統都支持“多任務”的操作
問題1:什麼時多任務
操作系統上同時運行多個任務,除了手動打開應用程序之外,還有很多的後臺程序
問題2:單核cpu是怎麼執行多任務
操作系統會讓多個任務輪流交替執行,由於不同的任務之間的切換速度很快,導致肉眼無法識別,
多核cpu實現多任務:真正的並行執行之惡能在多核cpu上執行,但是現實情況下,任務的數量可能會遠遠大於cpu的核心數量,操作系統會將每個任務調度,讓每個cpu核心上輪流執行
並行:真正一起執行,任務的數量小於cpu的核心數量[理想型]
併發:看上去一起執行,任務數量大於cpu的核心數量[現實型]
問題3:什麼是進程?
對於操作系統而言,一個任務就是一個進程[Process]
例如:打開一個瀏覽器啓動了一個瀏覽器進程,打開一個Word啓動了一個word進程,但是,一個進程可以同時幹多件事情,在一個進程的內部,要同時幹多個事情,就需要同時執行多個子任務,將一個進程中的子任務被稱爲線程[Thread]
進程本質
是對一個程序的運行狀態和佔用資源[cpu,內存]的描述
進程是程序的一個運動過程,他指的是從代碼到加載到最後代碼加載完畢的整個過程
進程的特點:
a.獨立性:不同的進程之間是相互獨立的,在操作系統中互不影響
b.動態性:進程一旦被啓動之後,在操作系統中並不是靜止不動的,一直處於動態狀態
c.併發性:在操作系統上交替執行
python多任務實現方式
a.多進程模式:啓動多個進程,每個進程雖然只有一個線程,通過該線程執行一個任務
b.多線程模式:啓動一個進程,該進程啓動多個線程,每個線程執行一個任務
c.協程模式:yield[函數生成器]
d:多進程模式+多線程模式:啓動多個進程,每個進程啓動多個線程,執行的任務的數量會更多
使用
單任務現象
import time
#單任務:一個函數執行了某個特定的功能
def run():
while True:
print("hello")
time.sleep(0.5)
if __name__ == "__main__":
while True:
print("main")
time.sleep(1)
run()
#說明:單任務現象代碼從上往下依次執行,前面的代碼執行完畢,後面的代碼纔有執行的機會
啓動多進程實現多任務
import time,os
#如果要實現多任務,Python中提供了相應的模塊
#每通過Process創建一個對象,則表示創建一個進程
from multiprocessing import Process
#子進程中的任務
def run(s):
#os.getpid()獲取當前正在執行的進程id
#os.getppid(獲取當前正在執行進程的父進程id
print("子進程啓動:%s-%s" % (os.getpid(),os.getppid()))
print(s)
while True:
print("hello")
time.sleep(1)
if __name__ == "__main__":
#1.__main__中執行的代碼表示主進程,也稱爲父進程
#os.getpid獲取當前正在執行的進程id
print("主進程啓動:%s" % (os.getpid()))
#2.在主進程中創建子進程
#Process(target,args),target表示子進程需要執行的任務【函數】,args表示需要執行的任務的參數,類型爲元組
#2.1任務函數沒有參數
#p1 = Process(target=run)
#2.2任務函數有參數
#注意:給子進程中的任務傳參的時候,函數有幾個參數,元組中的元素需要保持一致
p1 = Process(target=run,args=("text",))
#如果想要啓動一個子進程,則必須調用start
p1.start()
#主進程中的任務
while True:
print("main")
time.sleep(1)
run()
父子進程的執行順序
from multiprocessing import Process
import time,os
def run():
print("子進程啓動~~~~~")
time.sleep(2)
print("子進程結束~~~~~")
if __name__ == "__main__":
print("父進程啓動")
time.sleep(2)
p = Process(target=run)
p.start()
p.join()
print("父進程結束")
"""
默認情況下,都是主進程結束之後子進程才啓動
使用join之後,主進程會等待子進程結束之後才結束
注意:join的調用只能在start之後
"""
多個進程中的全局變量
from multiprocessing import Process
from time import sleep
import os
#需求:在一個進程中修改全局變量的值,觀察另一個進程中全局變量的值的變化
#全局變量
num = 100
def run():
print("子進程開始:%s" % (os.getpid()))
global num
num += 1
print("子進程結束:num=%d" % (num))
if __name__ == "__main__":
print("父進程開始:%s" % (os.getpid()))
p = Process(target=run)
p.start()
p.join()
print("父進程結束:num=%d" % (num))
"""
說明:在子進程中修改全局變量對父進程中的全局變量沒有影響
工作原理;在創建子進程的時候,實際上對全局變量做了一個備份,父進程中的num和子進程中的num是兩個不同的變量
進程是系統中程序執行和系統資源分配的基本單位,每個進程都有自己的數據段,代碼段,堆棧段
【進程之間是相互獨立的,資源不共享】
"""
驗證多個進程之間的獨立性
啓動大量子進程
如果要大量創建子進程,可以使用進程池的方式批量創建子進程
#進程池
from multiprocessing import Pool
import os,time,random
#需求:創建多個子進程,讓子進程執行同一個任務,根據傳入的參數作爲進程的名稱進行區分
def run(name):
print("子進程%s啓動:%s" % (name,os.getpid()))
#通過time計算耗時的時間差
start = time.time()
time.sleep(random.choice(range(1,4)))
end = time.time()
print("子進程%s結束,進程號:%s,耗時:%s" % (name, os.getpid(),end - start))
if __name__ == "__main__":
print("父進程啓動")
#創建多個進程,採用進程池的方式,通過系統類Pool創建多個進程對象
#Pool(num),num表示需要同時執行的任務的數量,可以省略,默認爲cpu的核心數量
p = Pool(8)
#通過循環的方式創建多個子進程
for i in range(5):
#創建子進程,放到進程池統一管理
#apply_async(target,args)
p.apply_async(run,args=(i,))
#必須在join之前調用close,調用close之後將不能再添加新的進程
p.close()
#進程池對象調用join,觸發進程的啓動,父進程會等待所有的子進程執行結束之後才結束
p.join()
print("父進程結束")
"""
如果要使用進程池創建多個進程,必須調用close和join
"""
#需求:實現文件內容的拷貝
import os,time
#實現文件的拷貝
def copyfile(rpath,wpath):
fr = open(rpath,"rb")
fw = open(wpath,"wb")
content = fr.read()
fw.write(content)
fr.close()
fw.close()
if __name__ == "__main__":
path = r"C:\Users\Administrator\Desktop\text1"
topath = r"C:\Users\Administrator\Desktop\text2"
#獲取path下面的所有的內容
filelist = os.listdir(path)
start = time.time()
for filename in filelist:
copyfile(os.path.join(path,filename),os.path.join(topath,filename))
end = time.time()
print("總耗時:%.2f" % (end - start))
# 需求:實現文件內容的拷貝
import os, time
from multiprocessing import Pool
# 實現文件的拷貝
def copyfile(rpath, wpath):
fr = open(rpath, "rb")
fw = open(wpath, "wb")
content = fr.read()
fw.write(content)
fr.close()
fw.close()
if __name__ == "__main__":
path = r"C:\Users\Administrator\Desktop\text1"
topath = r"C:\Users\Administrator\Desktop\text3"
# 獲取path下面的所有的內容
filelist = os.listdir(path)
p = Pool(4)
start = time.time()
for filename in filelist:
#copyfile(os.path.join(path, filename), os.path.join(topath, filename))
p.apply_async(copyfile,args=(os.path.join(path, filename),os.path.join(topath, filename)))
p.close()
p.join()
end = time.time()
print("總耗時:%.2f" % (end - start))
封裝進程對象
from multiprocessing import Process
import os,time
#1.自定義一個類,繼承自Process
class CustomProcess(Process):
#2.書寫構造函數,定義實例屬性,表示當前進程的名稱
def __init__(self,name):
#3.調用服了你的構造函數
Process.__init__(self)
self.name = name
#4.定義成員函數,任務的執行函數
def run(self):
print("子進程(%s-%s)啓動" % (self.name,os.getpid()))
#子進程的功能
time.sleep(2)
print("子進程(%s-%s)結束" % (self.name, os.getpid()))
if __name__ == "__main__":
print("父進程啓動")
p = CustomProcess("1111")
p.start()
p.join()
print("父進程結束")
進程之間的通信
Process之間是需要相互通信的,操作系統提供了很多的辦法實現進程間的通信,Python中的multiprocessing包裝了底層的機制,提供了Queue[隊列],
#需求:在主進程中創建兩個子進程,一個進程用來向隊列中寫數據,另外一個進程用來從隊列中讀數據
from multiprocessing import Process,Queue
import os,time,random
#1.寫數據的任務
def write(queue):
print("進程%s開始" % (os.getpid()))
print("開始寫入")
for value in ["A","B","C"]:
print("add %s to queue" % (value))
#向隊列中添加數據
queue.put(value)
#稍休息片刻,接着加
time.sleep(random.random())
print("進程%s結束" % (os.getpid()))
#2.讀數據的任務
def read(queue):
print("進程%s開始" % (os.getpid()))
print("開始讀取")
n = 0
while n < 3:
#從隊列中獲取數據
value = queue.get(True)
print("get %s from queue" % (value))
n += 1
print("進程%s結束" % (os.getpid()))
#3.在主進程中分別創建子進程,執行相應的任務
if __name__ == "__main__":
print("父進程啓動")
#3.1創建隊列對象,並傳參給每個進程
q = Queue()
#3.2創建進程對象,
pr = Process(target=read,args=(q,))
pw = Process(target=write,args=(q,))
#3.3啓動子進程,讓進行數據的讀寫
pw.start()
pr.start()
#3.4設置,讓所有子進程結束之後,才結束父進程
pr.join()
pw.join()
print("父進程結束")
二.線程
概念
是進程的組成部分,一個進程可以有多個線程,每個線程去處理一個特定的子任務
注意:一個進程至少需要一個線程,否則該進程沒有意義.
線程的執行是搶佔式的,多個線程在同一個進程中可以併發執行,其實就是在cpu之間進行快速的切換,[每個線程都有爭搶時間片的機會,誰搶到時間片,則執行對應的線程,剩下的線程都會被掛起]
例如: 打開網易雲音樂----->啓動一個進程
播放歌曲和刷新歌詞------->啓動了兩個線程
進程和線程的關係
a.一個程序啓動之後至少有一個進程
b.一個進程可以包含多個線程,但是至少需要一個線程,否則該進程沒有意義
c.進程之間的資源不共享,線程之間的資源是共享的
d.系統創建進程需要爲該進程重新分配系統資源,二創建線程容易的多,因此使用多線程實現多任務比多進程實現效率更高
創建線程
_thread模塊,提供了低級別的,原始的線程[低級別並不是不好,知識功能有限,底層採用的c語言]
threading模塊:高級模塊,對_thread進行了封裝,提供了_thread沒有的功能
import threading,time
#2.定義子線程需要執行的任務
def run(num):
print("子線程%s啓動" % (threading.current_thread().name))
time.sleep(2)
print("num=%d" % (num))
print("子線程%s結束" % (threading.current_thread().name))
if __name__ == "__main__":
#1.任何進程默認會啓動一個線程,稱爲主線程,在主線程中啓動其他的子線程
#threading.current_thread()返回一個當前正在執行的線程對象
#threading.current_thread().name獲取當前正在執行的線程的名稱,主線程默認的名稱爲MainThread
print("主線程%s啓動" % (threading.current_thread().name))
#3.創建子線程
#threading.Thread(target,name,args),target需要執行的任務,name表示線程的名稱,args執行的任務函數的參數
#3.1任務函數沒有參數
#注意:name也可以省略,每個子線程默認有名稱,根據線程被創建的順序,爲Thread-1,Thread-2.....
#t1 = threading.Thread(target=run,name="11111")
#3.2任務函數有參數
t1 = threading.Thread(target=run,args=(10,))
#4.手動啓動子線程
t1.start()
#5.想讓主線程等待子線程執行結束之後才結束,設置join
t1.join()
print("主線程%s結束" % (threading.current_thread().name))
線程中的數據共享
問題:在多線程中,全局變量被所有線程所共享,所以,任何一個全局變量可以被任何一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時修改同一個變量,容易將數據改亂
#需求:使用一個全局變量代表銀行卡餘額,兩個線程都可以訪問銀行卡的餘額
from threading import Thread
balance = 0
def change(n):
global balance
balance = balance + n
balance = balance - n
#子線程的任務
def run(n):
for _ in range(1000000):
change(n)
if __name__ == "__main__":
t1 = Thread(target=run,args=(5,))
t2 = Thread(target=run,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
#理論上,balance最終的結果爲0
print(balance)
"""
問題:
先加後減,理論上結果爲0,但是,如果訪問的次數變多,則結果不一定爲0
原因:
使用多線程容易出現的臨界資源問題
高級語言中一條語句分步執行的,所以balance = balance + n在內存中執行的順序是:
a.計算balance + n,將結果存儲到一個臨時變量中 x = balance + n
b.將臨時變量的值賦值給balance balance = x
1.正常情況下
t1:x = balance + n #5
t1:balance = x #balance = 5
t1:x = balance - n #0
t1:balance = x #0
t2:x = balance + n #8
t2:balance = x #balance = 8
t2:x = balance - n #0
t2:balance = x #0
2.實際情況下:
t1:x1 = balance + n #x1 = 5
t2:x2 = balance + n #x2 = 8
t2: balance = x2 #balance = 8
t1:balance = x1 #balance = 5
t1:x1 = balance - n #x1 = 0
t1:balance = x1 #balance = 0
t2:x2 = balance - n #x2 = -8
t2:balance = x2 #balance = -8
總結:修改balance需要多條語句,執行多條語句的時候,線程隨時可能會被中斷,從而導致多個線程將一個變量修改亂了
解決:上鎖【給多線程中的臨界資源上一把鎖,當一個線程訪問的時候,其他的線程則必須在鎖外面進行等待,直到鎖被釋放之後,
其他線程纔有再次爭搶時間片的機會】
"""
線程鎖
from threading import Thread,Lock
balance = 0
#創建一個鎖對象
lock = Lock()
def change(n):
global balance
balance = balance + n
balance = balance - n
#子線程的任務
def run(n):
for _ in range(1000000):
#獲取鎖
lock.acquire()
try:
# 臨界資源
change(n)
finally:
#釋放鎖
lock.release()
if __name__ == "__main__":
t1 = Thread(target=run,args=(5,))
t2 = Thread(target=run,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
#理論上,balance最終的結果爲0
print(balance)
from threading import Thread,Lock
balance = 0
#創建一個鎖對象
lock = Lock()
def change(n):
global balance
balance = balance + n
balance = balance - n
#子線程的任務
def run(n):
for _ in range(1000000):
#同樣表示獲取鎖,但是,不用手動釋放,當臨界資源的代碼執行完畢之後,鎖會被自動釋放
with lock:
# 臨界資源
change(n)
if __name__ == "__main__":
t1 = Thread(target=run,args=(5,))
t2 = Thread(target=run,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
#理論上,balance最終的結果爲0
print(balance)