多任務(wù):線程
多任務(wù):簡介
- 操作系統(tǒng)可以同時(shí)運(yùn)行多個(gè)任務(wù)。操作系統(tǒng)輪流讓各個(gè)任務(wù)交替執(zhí)行,實(shí)現(xiàn)的多任務(wù)效果
- 并發(fā):任務(wù)數(shù)多余CPU核數(shù),通過操作系統(tǒng)的各個(gè)任務(wù)調(diào)度算法,實(shí)現(xiàn)用多個(gè)任務(wù)“一起”執(zhí)行(多個(gè)任務(wù)交替執(zhí)行)
- 并行:任務(wù)數(shù)小于或等于CPU核心數(shù),即任務(wù)是一起執(zhí)行的
線程:簡介
- 線程是程序的最小執(zhí)行流單元,是程序中一個(gè)單一的順序控制流程
threading 模塊
- Python的thread模塊比較底層,而threading模塊是對thread進(jìn)行封裝,使便于使用
- 當(dāng)調(diào)用start()時(shí),才會(huì)真正創(chuàng)建線程,并且開始執(zhí)行。主線程會(huì)等待所有子線程結(jié)束后才結(jié)束
線程:語法
import threading
t = threading.Thread(target="函數(shù)名")
t.start()
查看線程數(shù)量
-
len(threading.enumerate()):查看當(dāng)前線程數(shù)量
線程執(zhí)行代碼的封裝
- 通過使用threading模塊能完成多任務(wù)的程序開發(fā),為了讓每個(gè)線程的封裝性更完美,所以使用threading模塊時(shí),往往會(huì)定義一個(gè)新的子類class,只要繼承
threading.Thread,然后重寫run方法
線程的執(zhí)行順序
- 多線程的執(zhí)行順序是不確定的。當(dāng)執(zhí)行到sleep語句時(shí),線程將會(huì)被阻塞(Blocked),到sleep結(jié)束后,線程進(jìn)入就緒(Runnable)狀態(tài),等待調(diào)度
- 每個(gè)線程默認(rèn)有一個(gè)名字,如果不指定線程對象的名字,解釋器會(huì)自動(dòng)為線程指定名字
- 當(dāng)線程的run()方法結(jié)束時(shí),該線程完成
- 無法控制線程調(diào)度程序,但可以通過其他方式影響線程調(diào)度方式
共享全局變量
- 在一個(gè)進(jìn)程內(nèi),所有線程共享全局變量,很方便多個(gè)線程之間的數(shù)據(jù)共享。缺點(diǎn)是:線程是對全局變量隨意更改,可能會(huì)造成多個(gè)線程之間對全局變量的混亂(線程不安全)
多線程開發(fā)問題
- 如果多個(gè)線程同時(shí)對同一個(gè)全局變量操作,會(huì)造成資源競爭,從而導(dǎo)致數(shù)據(jù)結(jié)果異常
互斥鎖
- 當(dāng)多個(gè)線程同時(shí)修改一個(gè)共享數(shù)據(jù)時(shí),需要進(jìn)行同步控制,線程同步能夠保證多個(gè)線程安全訪問,最簡單方式,就是引入互斥鎖
- 互斥鎖狀態(tài):鎖定/非鎖定
- 當(dāng)線程更改共享數(shù)據(jù)時(shí),會(huì)先進(jìn)行鎖定,防止其他線程同時(shí)更改;直到線程釋放資源,才會(huì)解除鎖定狀態(tài)
- 互斥鎖保證了每次只有一個(gè)線程進(jìn)行寫入操作,從而保證了多線程情況下數(shù)據(jù)的正確性
互斥鎖:語法
# 創(chuàng)建鎖
mutex = threading.Lock()
# 鎖定
mutext.acquire()
# 釋放
mutex.release()
上鎖解鎖過程
- 當(dāng)一個(gè)線程調(diào)用鎖的qcquire()方法獲得鎖時(shí),就會(huì)進(jìn)入“l(fā)ocked狀態(tài)”
- 每次只有一個(gè)線程可以獲得鎖,如果此時(shí)另一個(gè)線程試圖獲取這個(gè)鎖,該線程就會(huì)變?yōu)椤癰locked”狀態(tài),稱之為“阻塞”,直到擁有鎖的線程調(diào)用鎖的“release()”方法釋放鎖之后,鎖進(jìn)入“unlocked”狀態(tài)
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):確保了某段代碼只能由一個(gè)線程調(diào)用
- 缺點(diǎn):阻止了多線程并發(fā)執(zhí)行,包含鎖的某段代碼,實(shí)際上只能以單線程方式執(zhí)行,降低效率。由于可以存在多個(gè)鎖,不同的線程持有不同的鎖,并試圖獲取對方持有的鎖時(shí),易造成死鎖
死鎖
- 在線程間,共享多個(gè)資源的時(shí)候,如果兩個(gè)線程分別占有一部分資源,并且同時(shí)等待對方的資源,就會(huì)造成死鎖。一旦發(fā)生死鎖,程序就會(huì)停止響應(yīng)
避免死鎖
- 程序設(shè)計(jì)時(shí)要盡量避免
- 添加超時(shí)時(shí)間
GIL
- 全局解釋器鎖。每個(gè)線程執(zhí)行都必須先獲取GIL,保證同一時(shí)刻只有一個(gè)線程可以執(zhí)行代碼
多任務(wù):進(jìn)程
進(jìn)程:簡介
- 一個(gè)程序運(yùn)行起來后,代碼使用的系統(tǒng)資源稱之為“進(jìn)程”,它是操作系統(tǒng)分配資源的基本單元。不僅可以通過線程完成多任務(wù),進(jìn)程也可以
進(jìn)程 :狀態(tài)
- 就緒態(tài):滿足運(yùn)行條件,等待CPU執(zhí)行
- 執(zhí)行態(tài):CPU只在運(yùn)行其功能
- 等待態(tài):等待某些條件滿足
multiprocessing 模塊
- multoprocessing模塊是跨平臺版本的多進(jìn)程模塊,提供一個(gè)Process類來代表一個(gè)進(jìn)程對象,這個(gè)對象可以理解是一個(gè)獨(dú)立的程序,可以執(zhí)行其他功能
語法
from multiprocessiong import Process
p = Process(target="函數(shù)名")
p.start()
Process
Process([group[,name[,args[,kwargs]]]]) 參數(shù)
- target:如果傳遞了函數(shù)的引用,可以引用這個(gè)子進(jìn)程就執(zhí)行這里的代碼
- args:給target指定函數(shù)傳遞的參數(shù),以元組的方式傳遞
- kwargs:給target指定的函數(shù)傳遞命名參數(shù)
- name:給進(jìn)程設(shè)定一個(gè)名字,可以不設(shè)定,但系統(tǒng)會(huì)自動(dòng)指定一個(gè)進(jìn)程名
- group:指定進(jìn)程組
Process 常用方法
- start():啟動(dòng)子進(jìn)程實(shí)例(創(chuàng)建子進(jìn)程)
- is_alive():判斷進(jìn)程或子進(jìn)程是否存活
- join([timeout]):是否等待子進(jìn)程執(zhí)行結(jié)束,或等待多少秒
- terminate():不管任務(wù)是否完成,立即終止子進(jìn)程
Process 常用屬性
- name:當(dāng)前進(jìn)程的別名,默認(rèn)為Process-N,N為從1開始遞增的整數(shù)
- pid:當(dāng)前進(jìn)程的pid(進(jìn)程號)
Process獲取進(jìn)程號
進(jìn)程間通信
Queue
- 可以使用multiprocessing模塊的Queue方法來實(shí)現(xiàn)多進(jìn)程之間的數(shù)據(jù)傳遞,Queue本身就是一個(gè)消息隊(duì)列程序
- 初始化Queue()對象時(shí),若沒有指定最大可接收的消息數(shù)量,那么就表示可接收的消息數(shù)量沒有上線(直到內(nèi)存用完)
方法
- Queue.qsize():返回當(dāng)前隊(duì)列包含的消息數(shù)量
- Queue.empty():如果隊(duì)列為空,返回True,反之False
- Queue.full():如果隊(duì)列滿了,返回True,反之False
- Queue.get([block[,timeout]]):獲取隊(duì)列中的一條消息,然后將其從隊(duì)列中移除,block默認(rèn)值為True
- 如果block使用默認(rèn)值,且沒有設(shè)置timeout(單位秒),消息隊(duì)列如果為空,此時(shí)程序?qū)⒈蛔枞ㄍT谧x取狀態(tài)),直到從消息隊(duì)列讀完消息為止,如果設(shè)置了timeout,則會(huì)等待timeout秒,若什么都讀取不到,則拋出“Queue.Empty”異常
- 如果block值為False,消息隊(duì)列為空,則立即拋出“Queue.Empty”異常
- Queue.get_nowait():相當(dāng)于Queue.get(False)
- Queue.put(item,[block[,timeout]]):將item消息寫入隊(duì)列block默認(rèn)值為True
- 如果block使用默認(rèn)值,并且沒有設(shè)置timeout(單位秒),消息隊(duì)列沒有空間寫入,此時(shí)程序?qū)?huì)被阻塞(停在寫入狀態(tài)),直到消息隊(duì)列有空間,如果設(shè)置timeout,則會(huì)等待timeout秒,若還沒空間,則會(huì)拋出“Queue.Full”異常
- 如果block值為False,消息隊(duì)列沒有空間寫入,則會(huì)拋出“Queue.Full”異常
- Queue.put_nowait(item):相當(dāng)于Queue.put(item,False)
進(jìn)程池
- 當(dāng)需要?jiǎng)?chuàng)建的進(jìn)程數(shù)量非常多的時(shí)候,可以使用multiprocessing模塊提供的Pool方法
- 初始化Pool時(shí),可以指定一個(gè)最大進(jìn)程數(shù),當(dāng)有新的請求時(shí),提交到Pool中,如果池沒滿,那么就會(huì)創(chuàng)建一個(gè)新的進(jìn)程用來執(zhí)行該請求。如果池中的進(jìn)程數(shù)達(dá)到最大值,那么該請求就會(huì)等待,直到池中存在進(jìn)程結(jié)束,才會(huì)用來執(zhí)行新的進(jìn)程
- 如果使用Pool創(chuàng)建進(jìn)程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否則會(huì)報(bào)錯(cuò)
multiprocessing.Pool 常用函數(shù)
- apply_async(func[,args[,kwds]]):使用非阻塞方式調(diào)用func(并行執(zhí)行,阻塞方式必須等待上一個(gè)進(jìn)程推出才能執(zhí)行下一個(gè)進(jìn)程),args為傳遞給func的參數(shù)列表,kwds為傳遞給func的關(guān)鍵字參數(shù)列表
- close():關(guān)閉Pool,使其不再接受新的任務(wù)
- terminate():不管任務(wù)是否完成,立即終止
- join():主進(jìn)程阻塞,等待子進(jìn)程退出,必須在close或terminate之后使用
多任務(wù):協(xié)程
協(xié)程:簡介
- 協(xié)程,又被稱為微線程,纖程。協(xié)程是Python中另外一種實(shí)現(xiàn)多任務(wù)的方式,只不過比線程更小,占用更小的執(zhí)行單元。它自帶CPU上下文,在合適的時(shí)候可以把一個(gè)協(xié)程切換到另一個(gè)協(xié)程中,這個(gè)過程中恢復(fù)CPU上下文,程序還可以繼續(xù)運(yùn)行
簡單實(shí)現(xiàn)協(xié)程
import time
def work1():
while True:
print("-------work1------")
yield
time.sleep(0.5)
def work2():
while True():
print("--------work2------")
yield
time.sleep(0.5)
def main():
w1 = work1()
w2 = work2()
while True:
next(w1)
next(w2)
if __name__ == '__main__':
main()
greenlet
- greenlet模塊對協(xié)程進(jìn)行封裝,而使切換任務(wù)變得更加簡單,遇到IO操作時(shí),會(huì)等待IO操作完成,才會(huì)在適當(dāng)?shù)臅r(shí)候切換回來,繼續(xù)運(yùn)行,非常耗時(shí)
安裝:greenlet
sudo pip3 install greenlet
greenlet簡單實(shí)現(xiàn)
from greenlet import greenlet
import time
def test1():
while True:
print("----------A------------")
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print("---------------B-----------")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
# 切換到gr1中運(yùn)行
gr1.switch()
gevent
- 能夠自動(dòng)切換任務(wù)的模塊
gevent,保證總有g(shù)reenlet一直在運(yùn)行
gevent: 安裝
pip3 install gevent
gevent:簡單實(shí)現(xiàn)
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()
Monkey給程序打補(bǔ)丁
from gevent import monkey
import gevent
import random
import time
# 有耗時(shí)操作時(shí),需要將程序用到的耗時(shí)操作代碼,換成gevent中自己實(shí)現(xiàn)的模塊
monkey.patch_all()
def coroutine_work(coroutime_name):
for i in range(10):
print(coroutime_name,i)
time.sleep(random.random())
gevent.joinall([
gevent.spawn(coroutine_work,"work1"),
gevent.spawn(coroutine_work,"work2")
])
線程和線程
- 在實(shí)現(xiàn)多任務(wù)時(shí),線程切換非常消耗性能,需要保存很多數(shù)據(jù),而協(xié)程的切換只需要操作CPU的上下文,速度快
迭代
- 迭代是一個(gè)可以記住遍歷位置的對象。迭代器對象從集合的第一個(gè)元素開始訪問,直到所有的元素被訪問完,才會(huì)結(jié)束
可迭代對象
- 使用for...in... 這類語句迭代讀取一條數(shù)據(jù),并可以使用的對象稱之為可迭代對象(lterble)。可以使用
isinstance()判斷一個(gè)對象是否是可迭代對象
可迭代對象的本質(zhì)
- 迭代過程:使用
for...in...或者其他循環(huán)來執(zhí)行迭代,進(jìn)行所有的數(shù)據(jù)獲取,一般數(shù)據(jù)都是連續(xù)的
- 幫助進(jìn)行迭代的工具,稱之為迭代器。迭代器可以幫助遍歷所有數(shù)據(jù)
- 可迭代對象通過
__iter__方法提供一個(gè)迭代器。在迭代對象時(shí),實(shí)際上是獲取該對象的迭代器,然后通過這個(gè)迭代器來依次獲取對象中的每個(gè)數(shù)據(jù)
- 可以通過
iter()函數(shù)獲取這些可迭代對象的迭代器,然后可以對獲取到的迭代器不斷使用next()函數(shù)來獲取下一條數(shù)據(jù)。當(dāng)?shù)瓿珊螅僬{(diào)用next()函數(shù)會(huì)拋出Stoplteration的異常,表示所有數(shù)據(jù)已迭代完成,不再執(zhí)行next()函數(shù)
for ...in...循環(huán)的本質(zhì)
-
for item in lterable循環(huán)的本質(zhì)就是通過iter()函數(shù)獲取可迭代對象的迭代器,然后對獲取到的迭代器不斷調(diào)用next()方法來獲取下一個(gè)值,并將其賦值給item,當(dāng)遇到Stoplteration的異常后循環(huán)結(jié)束
生成器
生成器:簡介
- 生成器是一類特殊的迭代器。在實(shí)現(xiàn)一個(gè)迭代器的時(shí)候,當(dāng)前迭代器的狀態(tài)需要自己記錄,才能根據(jù)當(dāng)前狀態(tài)生成下一個(gè)數(shù)據(jù)。在def中有yield函數(shù)被稱為生成器
- 將原本在迭代器
__next__方法中實(shí)現(xiàn)的基本邏輯放在一個(gè)函數(shù)中實(shí)現(xiàn),但是將每次迭代返回?cái)?shù)值的return換成yield,此時(shí)新定義的函數(shù)便不再是函數(shù),而是一個(gè)生成器
創(chuàng)建生成器
- 把一個(gè)列表生成式的[]改成(),類似生成器
A = [x*2 for in range(5)] # 列表生成式
B = (x*2 for in range(5)) #
next(B) # 使用
生成器實(shí)現(xiàn)斐波那切數(shù)列
def fib(n):
current = 0
num1,num2 = 0 , 1
while current < n:
num = num1
num1,num2 = num2,num1+num2
current += 1
yield num
return 'done'
使用生成器
for n in fib(5):
print(n)
捕獲生成器錯(cuò)誤
- 使用for循環(huán)調(diào)用generator時(shí),發(fā)現(xiàn)拿不到generator的return語句的返回值。返回值包含在Stoplteration的value中
g = fib(5)
while True:
try:
x = next(g)
print("value:%d"%x)
except StopIteration as Stop:
print("生成器返回值:%s"%Stop.value)
break
send喚醒
- 除了使用next()函數(shù)進(jìn)行生成器喚醒繼續(xù)執(zhí)行外,還可以通過send()函數(shù)來喚醒執(zhí)行。
- 使用send()函數(shù)的好處是,可以在喚醒時(shí)同時(shí)向斷點(diǎn)處傳入一個(gè)附加數(shù)據(jù)
使用send
def gen():
i = 0
while i<5:
temp = yield i
print(temp)
i += 1
f =gen()
f.send("附加數(shù)據(jù)")
進(jìn)程、線程、協(xié)程對比
- 進(jìn)程是資源分配單位
- 線程是操作系統(tǒng)調(diào)度的單位
- 進(jìn)程切換需要的資源很大,并且效率低
- 線程切換需要的資源一般,并且效率一般
- 協(xié)程切換任務(wù)資源很小,效率高
- 多進(jìn)程、多線程,是根據(jù)CPU核心數(shù)一樣,可能是并行,但是協(xié)程是在一個(gè)線程中,所以是并發(fā)