12-Python之路-進(jìn)階-多任務(wù)

多任務(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)程號
  • os.getpid()獲取進(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ā)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

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

  • 一 、實(shí)現(xiàn)多任務(wù)的方式 多線程多進(jìn)程協(xié)程多線程+多進(jìn)程 并行,并發(fā) 并行:同時(shí)發(fā)起同時(shí)執(zhí)行,(4核,4個(gè)任務(wù))并發(fā)...
    changzj閱讀 576評論 0 0
  • 什么是多任務(wù)?就是操作系統(tǒng)可以同時(shí)運(yùn)行多個(gè)任務(wù) 多任務(wù)的執(zhí)行方式并發(fā):交替執(zhí)行,是假的多任務(wù)并行:同時(shí)執(zhí)行,是真的...
    三點(diǎn)四行間閱讀 1,281評論 0 0
  • 多任務(wù)什么是多任務(wù)同時(shí)做多件事件(做個(gè)多個(gè)任務(wù)),運(yùn)行多個(gè)方法多任務(wù)的原理并發(fā):假的多任務(wù),時(shí)間片的輪轉(zhuǎn),快速的交...
    Ives247閱讀 1,103評論 0 0
  • 系統(tǒng)編程:多任務(wù)編程 1. 線程: 可以理解成執(zhí)行代碼的分支,線程是執(zhí)行對應(yīng)的代碼的 1.1 線程的工作原理: ...
    夢醒家先生閱讀 1,135評論 2 0
  • Python語言進(jìn)階 數(shù)據(jù)結(jié)構(gòu)和算法算法:解決問題的方法和步驟評價(jià)算法的好壞:漸近時(shí)間復(fù)雜度和漸近空間復(fù)雜度。漸近...
    赤劍吟龍閱讀 606評論 0 0

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