python之進(jìn)程、線程與協(xié)程

python之進(jìn)程备埃、線程與協(xié)程

有這么個(gè)例子說(shuō)他們的區(qū)別姓惑,幫助理解很有用。

  • 有一個(gè)老板想開(kāi)一個(gè)工廠生產(chǎn)手機(jī)按脚。
  • 他需要花一些財(cái)力于毙、物力及人力去制作一條生產(chǎn)線,這條線上所有的錢辅搬、人唯沮、物料準(zhǔn)備:為了生產(chǎn)手機(jī)的儲(chǔ)備資源稱之為進(jìn)程
  • 有了生產(chǎn)線堪遂,老板負(fù)責(zé)把資源統(tǒng)一管理調(diào)度起來(lái)介蛉,然后還需要一個(gè)工人按照生產(chǎn)手機(jī)的工藝、去按部就班地把手機(jī)做出來(lái)溶褪。這個(gè)做事情的工人就叫線程币旧。
  • 然后生產(chǎn)線可以運(yùn)作起來(lái)了,但是效率低猿妈,手機(jī)供不應(yīng)求吹菱。為了提高生產(chǎn)效率巍虫,老板又想了幾個(gè)辦法;
    • 在這條生產(chǎn)線上多招幾個(gè)工人毁葱,一起來(lái)做手機(jī)垫言,這樣效率就增加了。即單進(jìn)程倾剿、多線程模式
    • 之后老板又發(fā)現(xiàn)蚌成,單條生產(chǎn)線的工人并不是越多越好前痘,因?yàn)橐粭l生產(chǎn)線的資源、空間等有限担忧,所以老板又花了財(cái)力物力去置辦了另外一條生產(chǎn)線芹缔,然后在招一些工人,效率又上去了瓶盛。即多進(jìn)程最欠、多線程模式
    • 現(xiàn)在已經(jīng)有個(gè)多條生產(chǎn)線和多個(gè)工人(多進(jìn)程惩猫、每個(gè)進(jìn)程對(duì)應(yīng)多條線程)芝硬。但是老板發(fā)現(xiàn),有時(shí)工人按照生產(chǎn)手機(jī)的工藝工作時(shí)轧房,有時(shí)是為了等待上一道工序的完成是閑下來(lái)的拌阴,就侃大山去了。資本家么奶镶,就想了一個(gè)辦法:如果某個(gè)員工在工作時(shí)臨時(shí)沒(méi)事或者在等待另一個(gè)工人生產(chǎn)完謀道工序之后他才能再次工作迟赃,那么這個(gè)員工就利用這個(gè)時(shí)間去做其它的事情。那么也就是說(shuō):如果一個(gè)線程等待某些條件厂镇,可以充分利用這個(gè)時(shí)間去做其它事情纤壁。即協(xié)程

這里再總結(jié)術(shù)語(yǔ)有下面幾點(diǎn):

  • 進(jìn)程是資源分配和調(diào)度資源的最小單位捺信。
  • 線程是進(jìn)程的實(shí)體酌媒,是系統(tǒng)分配和調(diào)度的基本單位。
  • 進(jìn)程切換需要的資源很最大残黑,效率很低馍佑。
  • 線程切換需要的資源一般,效率一般(當(dāng)然了在不考慮GIL的情況下)梨水。
  • 協(xié)程切換任務(wù)資源很小拭荤,效率高。
  • 多進(jìn)程疫诽、多線程根據(jù)cpu核數(shù)不一樣可能是并行的舅世,但是協(xié)程是在一個(gè)線程中旦委,所以是并發(fā)。

1雏亚、多任務(wù)的需求

在演唱會(huì)中缨硝,很多歌手都是歌舞一起表演,那加入用程序來(lái)模擬的話罢低,就大概是這樣查辩。

from time import sleep


def sing():
    for i in range(3):
        print('我在唱歌')
        sleep(1)


def dance():
    for i in range(3):
        print('我在跳舞')
        sleep(1)


if __name__ == '__main__':
    sing()
    dance()

結(jié)果:

我在唱歌
我在唱歌
我在唱歌
我在跳舞
我在跳舞
我在跳舞

上面運(yùn)行的程序并沒(méi)有完成唱歌和跳舞同時(shí)進(jìn)行的要求,那如果想要實(shí)現(xiàn)“唱歌跳舞”同時(shí)進(jìn)行网持,那么就需要一個(gè)新的方方法:多任務(wù)宜岛。

那什么叫多任務(wù)。簡(jiǎn)而言之就是系統(tǒng)可以同時(shí)運(yùn)行多個(gè)任務(wù)功舀。比如你聊天的時(shí)候可以聽(tīng)歌萍倡,還可以瀏覽網(wǎng)頁(yè)等。

要實(shí)現(xiàn)它有很多方式辟汰,以前是單核cpu列敲,由于cpu執(zhí)行代碼都是順序執(zhí)行的,那么單核cpu就輪流讓各個(gè)任務(wù)交替執(zhí)行帖汞,這樣反復(fù)執(zhí)行下去戴而。表面上看,每個(gè)任務(wù)都是交替執(zhí)行的涨冀,但是填硕,由于cpu的執(zhí)行速度比較快,我們感覺(jué)就像所有任務(wù)都在同時(shí)執(zhí)行一樣鹿鳖。

真正的并行執(zhí)行多任務(wù)只能在多核cpu上實(shí)現(xiàn)扁眯,但是由于任務(wù)數(shù)量遠(yuǎn)遠(yuǎn)多于cpu的核心數(shù)量,所以系統(tǒng)也會(huì)自動(dòng)把很多任務(wù)輪流調(diào)度到每個(gè)核心上執(zhí)行翅帜。

這里涉及到兩個(gè)概念姻檀。

  • 并發(fā):任務(wù)數(shù)多于cpu核數(shù),通過(guò)操作系統(tǒng)的各種任務(wù)調(diào)度算法涝滴,實(shí)現(xiàn)用多個(gè)任務(wù)“一起”執(zhí)行(實(shí)際上一些任務(wù)不在執(zhí)行绣版,因?yàn)榍袚Q任務(wù)的速度相當(dāng)快,看上去一起執(zhí)行而已)歼疮。
  • 并行:任務(wù)數(shù)小于等于cpu核數(shù)杂抽,即任務(wù)真的是一起執(zhí)行的。

2韩脏、線程

python的thread模塊是比較底層的模塊缩麸,python的threading模塊是對(duì)thread做了一些封裝,可以更加方便的被使用赡矢。

2.1 使用threading模塊

1杭朱、單線程執(zhí)行一個(gè)任務(wù)

from time import sleep


def study_apologise():
    print('鋼鐵直男是要學(xué)會(huì)道歉的阅仔。')
    sleep(1)


if __name__ == '__main__':
    for i in range(5):
        study_apologise()

結(jié)果就不少于5s學(xué)習(xí)時(shí)間。

2弧械、多線程執(zhí)行一個(gè)任務(wù)

import threading
from time import sleep


def study_apologise():
    print('鋼鐵直男是要學(xué)會(huì)道歉的八酒。')
    sleep(1)


if __name__ == '__main__':
    for i in range(5):
        muti_threaing = threading.Thread(target=study_apologise)
        muti_threaing.start()

結(jié)果就秒出了,可以明顯看出使用了多線程并發(fā)的操作刃唐,花費(fèi)時(shí)間要短很多羞迷。且當(dāng)調(diào)用start()時(shí),才會(huì)真正的創(chuàng)建線程画饥,并且開(kāi)始執(zhí)行闭树。

3、主線程會(huì)等待所有的子線程結(jié)束后才結(jié)束

from time import sleep
import threading


def sing():
    for i in range(3):
        print('我在唱歌')
        sleep(1)


def dance():
    for i in range(3):
        print('我在跳舞')
        sleep(1)


if __name__ == '__main__':
    my_sing = threading.Thread(target=sing)
    my_dance = threading.Thread(target=dance)
    my_sing.start()
    my_dance.start()
    # sleep(5)
    print('---結(jié)束---')

這里主進(jìn)程會(huì)一直向下進(jìn)行荒澡,子進(jìn)程代碼繼續(xù)運(yùn)行,當(dāng)所有子進(jìn)程結(jié)束后与殃,主進(jìn)程才會(huì)結(jié)束单山。

4、查看線程數(shù)量

from time import sleep
import threading


def sing():
    for i in range(3):
        print('我在唱歌')
        sleep(1)


def dance():
    for i in range(3):
        print('我在跳舞')
        sleep(1)


if __name__ == '__main__':
    my_sing = threading.Thread(target=sing)
    my_dance = threading.Thread(target=dance)
    my_sing.start()
    my_dance.start()
    # sleep(5)
    while True:
        thread_count = len(threading.enumerate())
        print('當(dāng)前的進(jìn)程數(shù)為 %d' % thread_count)
        if thread_count <= 1:
            break
        sleep(0.5)

結(jié)果為:

我在唱歌 0
我在跳舞 0
當(dāng)前的進(jìn)程數(shù)為 3
當(dāng)前的進(jìn)程數(shù)為 3
當(dāng)前的進(jìn)程數(shù)為 3
我在跳舞 1
我在唱歌 1
當(dāng)前的進(jìn)程數(shù)為 3
我在唱歌 2
我在跳舞 2
當(dāng)前的進(jìn)程數(shù)為 3
當(dāng)前的進(jìn)程數(shù)為 3
當(dāng)前的進(jìn)程數(shù)為 2
當(dāng)前的進(jìn)程數(shù)為 1

2.2 線程執(zhí)行代碼的封裝

在上面可以體現(xiàn)出幅疼,通過(guò)threading模塊能完成多任務(wù)的程序開(kāi)發(fā)米奸。為了讓每個(gè)線程封裝的更完美,在實(shí)際使用threading模塊時(shí)爽篷,通常會(huì)定義一個(gè)新的子類悴晰,這個(gè)子類繼承threading.Thread,然后重寫(xiě)它的run方法逐工,上面的這個(gè)例子就可以寫(xiě)成下面這樣铡溪。

from time import sleep
import threading


class Sing(threading.Thread):
    def run(self):
        for i in range(3):
            print('我在唱歌 %d' % i)
            sleep(1)


class Dance(threading.Thread):
    def run(self):
        for i in range(3):
            print('我在跳舞 %d' % i)
            sleep(1)


if __name__ == '__main__':
    my_sing = Sing()
    my_dance = Dance()
    my_sing.start()
    my_dance.start()
    # sleep(5)
    while True:
        thread_count = len(threading.enumerate())
        print('當(dāng)前的進(jìn)程數(shù)為 %d' % thread_count)
        if thread_count <= 1:
            break
        sleep(0.5)

結(jié)果是一樣的。

再一個(gè)小demo泪喊。

from time import sleep
import threading


class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            # name屬性中保存的是當(dāng)前線程的名字
            print("this is %d thread and it's name is %s" % (i, self.name))
            sleep(1)


if __name__ == '__main__':
    my_thread = MyThread()
    my_thread.start()

threading.Tread類中有一個(gè)run方法棕硫,用于定義線程的功能函數(shù),可以在自己的線程類中覆蓋該方法袒啼。創(chuàng)建自己的線程實(shí)例對(duì)象后哈扮,可以通過(guò)Tread的start方法,可以啟動(dòng)該線程蚓再。當(dāng)線程獲得執(zhí)行的機(jī)會(huì)時(shí)滑肉,就會(huì)調(diào)用run方法。

  • 每個(gè)線程都有一個(gè)名字摘仅,盡管上面的例子中沒(méi)有指定線程對(duì)象的name靶庙,但python會(huì)自動(dòng)為線程指定一個(gè)名字。
  • 當(dāng)線程的run方法完成時(shí)实檀,該線程完成惶洲。
  • 程序無(wú)法控制線程的調(diào)度順序按声,但可以通過(guò)別的方式去影響線程調(diào)度的方式。

2.3 線程:全局作用變量共享

from time import sleep
import threading

my_num = 10


class MyThreadOne(threading.Thread):
    def run(self):
        global my_num
        for i in range(5):
            my_num += 1
        print('線程1執(zhí)行后 %d' % my_num)


class MyThreadTwo(threading.Thread):
    def run(self):
        global my_num
        print('線程2執(zhí)行后 %d' % my_num)


if __name__ == '__main__':
    print('多線程執(zhí)行前 %d' % my_num)
    my_thread_one = MyThreadOne()
    my_thread_one.start()
    sleep(2)
    my_thread_two = MyThreadTwo()
    my_thread_two.start()

結(jié)果如下恬吕,很明顯地看出線程之間是共享全局變量的签则。

多線程執(zhí)行前 10
thread_one, my_num is 15---
線程1執(zhí)行后 15
thread_two, my_num is 15---
線程2執(zhí)行后 15

當(dāng)列表當(dāng)作實(shí)參傳遞到線程中。

from time import sleep
import threading

my_num = [11, 22, 33]


class MyThreadOne(threading.Thread):
    def __init__(self, args=()):
        self.num = args[0]
        super(MyThreadOne, self).__init__()

    def run(self):
        self.num.append(44)
        print('線程1執(zhí)行后 ', my_num)


class MyThreadTwo(threading.Thread):
    def run(self):
        print('線程2執(zhí)行后', my_num)
        pass
        


if __name__ == '__main__':
    print('多線程執(zhí)行前 ', my_num)
    my_thread_one = MyThreadOne(args=(my_num,))
    my_thread_one.start()
    sleep(2)
    my_thread_two = MyThreadTwo(args=(my_num,))
    my_thread_two.start()
  • 在一個(gè)進(jìn)程內(nèi)所有線程共享全局變量铐料,很方便在多個(gè)進(jìn)程間進(jìn)行共享數(shù)據(jù)渐裂。
  • 優(yōu)劣并存的是線程對(duì)全局變量的隨意修改可能會(huì)造成多線程之間對(duì)全局變量的混亂。

共享造成的混亂钠惩,還是上面類似的代碼:

from time import sleep
import threading

my_num = 100000


class MyThreadOne(threading.Thread):
    def run(self):
        global my_num
        for i in range(100000):
            my_num += 1
        print('線程1執(zhí)行后 %d' % my_num)


class MyThreadTwo(threading.Thread):
    def run(self):
        global my_num
        for i in range(100000):
            my_num += 1
          print('線程2執(zhí)行后 %d' % my_num)


if __name__ == '__main__':
    print('多線程執(zhí)行前 %d' % my_num)
    my_thread_one = MyThreadOne()
    my_thread_one.start()
    sleep(2)
    my_thread_two = MyThreadTwo()
    my_thread_two.start()

結(jié)果這樣:

多線程執(zhí)行前 100000
線程1執(zhí)行后 159679
線程2執(zhí)行后 259832

每次運(yùn)行不一樣柒凉,原因如下:

1、在my_num=0時(shí)篓跛,線程1取得my_num=0膝捞。此時(shí)系統(tǒng)把線程1調(diào)度為‘sleeping’狀態(tài),把線程2轉(zhuǎn)換為‘running’狀態(tài)愧沟,t2也獲得my_num=0蔬咬。
2、線程2對(duì)得到的值進(jìn)行加1并賦給my_num沐寺,使得my_num=1林艘。
3、系統(tǒng)又把線程2調(diào)度為‘sleeping’混坞,把線程1轉(zhuǎn)為‘running’狐援。線程線程1又把它之前得到的0加1后賦值給my_num。
4究孕、這樣導(dǎo)致線程1和2都對(duì)my_num加1啥酱,但結(jié)果仍然是my_num=1。

重點(diǎn):如果多個(gè)線程同時(shí)對(duì)同一個(gè)全局變量進(jìn)行操作蚊俺,會(huì)出現(xiàn)資源競(jìng)爭(zhēng)問(wèn)題懈涛,從而導(dǎo)致運(yùn)算結(jié)果會(huì)不正確。

2.4 同步與互斥鎖

同步不是說(shuō)一起同時(shí)泳猬,而是協(xié)同配合批钠。比如上面的問(wèn)題,線程1得封、2在操作my_num時(shí)埋心,發(fā)現(xiàn)對(duì)方在用含长,就延遲一會(huì)再用栏妖,相互等待,兩個(gè)同時(shí)只執(zhí)行1個(gè)就可以做到同步攒驰。

線程同步同步的實(shí)現(xiàn)用到互斥鎖

當(dāng)多個(gè)線程同時(shí)修改某一個(gè)共享數(shù)據(jù)時(shí)茬斧,就要進(jìn)行同步控制腰懂。最簡(jiǎn)單的機(jī)制就是互斥鎖。互斥鎖為資源的引入狀態(tài):鎖定/非鎖定项秉。

某個(gè)線程需要修改一個(gè)共享數(shù)據(jù)時(shí)绣溜,先將這個(gè)數(shù)據(jù)進(jìn)行“鎖定”,然后對(duì)其做修改娄蔼。這個(gè)共享數(shù)據(jù)被鎖定時(shí)怖喻,其它線程不能修改,知道該數(shù)據(jù)的狀態(tài)變成“非鎖定”岁诉,其它線程才能再次鎖定該資源锚沸。互斥鎖保證每個(gè)只有一個(gè)線程進(jìn)行寫(xiě)入操作涕癣,從而保證了多線程情況下數(shù)據(jù)的準(zhǔn)確性哗蜈。

threading.Lock封裝了互斥鎖,可以方便的對(duì)其進(jìn)行操作坠韩。方法如下:

mutex = threading.Lock()
mutex.acquire()
mutex.release()
  • 如果這個(gè)資源之前是沒(méi)上鎖的恬叹,那么acquire()不會(huì)堵塞。
  • 如果調(diào)用acquire()對(duì)這個(gè)資源進(jìn)行上鎖之前同眯,它已經(jīng)被其它線程上了鎖,那么此時(shí)acquire()會(huì)堵塞唯鸭,直到這個(gè)鎖被釋放為止须蜗。

按這樣的步驟再運(yùn)行上面數(shù)據(jù)運(yùn)算失敗的例子,如下目溉,最后得到正確的結(jié)果明肮。

多線程執(zhí)行前 100000
線程1執(zhí)行后 258195
線程2執(zhí)行后 300000

上鎖解鎖的過(guò)程:當(dāng)一個(gè)線程調(diào)用鎖的acquire()方法獲得鎖,鎖就進(jìn)入“l(fā)ocked”的狀態(tài)缭付。每次只有一個(gè)線程可以獲得鎖柿估。如果另一個(gè)線程試圖獲得這個(gè)鎖,該線程會(huì)變?yōu)椤癰locked”陷猫,成為堵塞秫舌,直到擁有鎖的線程的release()方法釋放鎖后,鎖進(jìn)入“unlock”狀態(tài)绣檬。線程調(diào)度程序從出于堵塞狀態(tài)中的線程選擇一個(gè)獲得鎖足陨,并使得該線程進(jìn)入到運(yùn)行“running”狀態(tài)。

特點(diǎn):

  • 某段關(guān)鍵代碼只能由一個(gè)線程從頭到尾地完整執(zhí)行娇未。
  • 阻止了多線程的并發(fā)執(zhí)行墨缘,包含鎖的某段代碼實(shí)際上就只能單線程模式執(zhí)行,效率降低。
  • 由于可以存在多個(gè)鎖镊讼,不同的線程持有不同的鎖宽涌,并試圖獲取對(duì)方持有的鎖時(shí),可能會(huì)造成死鎖蝶棋。

2.5 死鎖

在線程間共享多個(gè)資源的時(shí)候卸亮,如果兩個(gè)線程分別占有一部分資源并等待對(duì)方的資源,這樣就會(huì)造成死鎖嚼松。盡管死鎖很少發(fā)送嫡良,但一旦發(fā)生就會(huì)造成應(yīng)用停止響應(yīng)。

from time import sleep
import threading

my_num = 100000
mutex_a = threading.Lock()
mutex_b = threading.Lock()


def thread_a():
    mutex_a.acquire()
    print('獲取到了a鎖献酗,2s后嘗試獲取b鎖')
    sleep(2)
    mutex_b.acquire()
    print('已獲取b鎖')
    mutex_b.release()
    mutex_a.release()


def thread_b():
    mutex_b.acquire()
    print('獲取到了b鎖寝受,2s后嘗試獲取a鎖')
    sleep(2)
    mutex_a.acquire()
    print('已獲取a鎖')
    mutex_a.release()
    mutex_b.release()


if __name__ == '__main__':
    print('死鎖模擬')
    a = threading.Thread(target=thread_a)
    a.start()
    b = threading.Thread(target=thread_b)
    b.start()

運(yùn)行結(jié)果如下:

死鎖模擬
獲取到了a鎖,2s后嘗試獲取b鎖
獲取到了b鎖罕偎,2s后嘗試獲取a鎖

這樣一來(lái)很澄,就要考慮如何避免死鎖,通常兩種方案颜及。

  • 程序設(shè)計(jì)盡量避免(銀行家算法甩苛、生產(chǎn)者與消費(fèi)者)
  • 添加超時(shí)時(shí)間,釋放鎖

2.6 Condition與生產(chǎn)者與消費(fèi)者問(wèn)題

python提供的threading.Condition對(duì)象提供了對(duì)復(fù)雜線程同步支持的問(wèn)題俏站。

Condition被稱為條件變量讯蒲,除了提供與Lock類似的acquire、release方法之外肄扎,還提供了wait和notify方法墨林。

使用情景:線程首先acquire獲取條件變量,然后再進(jìn)行一些條件判斷犯祠,如果條件滿足旭等,則進(jìn)行一些處理改變條件后再通過(guò)notify方法通知其它線程,其它出于wait狀態(tài)的線程接到條件后會(huì)重新進(jìn)行條件判斷衡载。若條件不滿足則wait搔耕,等待notify通知。如此不斷反復(fù)這一過(guò)程痰娱。

那經(jīng)典的生產(chǎn)者與消費(fèi)者的問(wèn)題描述的是:假設(shè)一群生產(chǎn)者(Producer)和一群消費(fèi)者(Consumer)通過(guò)一個(gè)市場(chǎng)來(lái)交換東西弃榨。生產(chǎn)者的策略是如果市場(chǎng)的剩余產(chǎn)品少于5個(gè),那么就生產(chǎn)一個(gè)產(chǎn)品放到市場(chǎng)上梨睁。而消費(fèi)者的策略是如果市場(chǎng)上剩余的產(chǎn)品多于5個(gè)惭墓,就消費(fèi)1個(gè)產(chǎn)品。

from time import sleep
import threading
import random

MAX_SIZE = 5
SHARE_Q = []


class Producer(threading.Thread):
    def run(self):
        products = range(MAX_SIZE)
        global SHARE_Q
        while True:
            condition.acquire()
            if len(SHARE_Q) == MAX_SIZE:
                print('Market is full')
                condition.wait()
                print('Consumer must do shmething')
            else:
                product = random.choice(products)
                SHARE_Q.append(product)
                print("Producer: ", product)
                condition.notify()
            condition.release()
            sleep(random.random())


class Consumer(threading.Thread):
    def run(self):
        global SHARE_Q
        while True:
            condition.acquire()
            if not SHARE_Q:
                print('Market is empty')
                condition.wait()
                print('Producer must do shmething')
            else:
                product = SHARE_Q.pop()
                print("Consumer: ", product)
                condition.notify()
            condition.release()
            sleep(random.random())


if __name__ == '__main__':
    print('生產(chǎn)者與消費(fèi)者')
    condition = threading.Condition()
    a = Producer()
    a.start()
    b = Consumer()
    b.start()

2而姐、進(jìn)程

2.1 進(jìn)程及進(jìn)程的狀態(tài)

1腊凶、進(jìn)程

程序:這是一個(gè)py跑的程序,指一個(gè)靜態(tài)的概念。

進(jìn)程:一個(gè)程序運(yùn)行起來(lái)之后钧萍,運(yùn)行的代碼和用到的資源稱之為進(jìn)程褐缠,它是操作系統(tǒng)分配資源的基本單位。

2风瘦、進(jìn)程的狀態(tài)

一般的計(jì)算機(jī)運(yùn)行情況是任務(wù)數(shù)大于cpu數(shù)队魏,即一些任務(wù)正在運(yùn)行,另一些任務(wù)在等待cpu執(zhí)行的情況万搔,因此導(dǎo)致了任務(wù)有不同的狀態(tài)胡桨。

  • 就緒態(tài):運(yùn)行的條件都已經(jīng)滿足,正在等在cpu執(zhí)行瞬雹。
  • 執(zhí)行態(tài):cpu正在執(zhí)行其功能昧谊。
  • 等待態(tài):等待某些條件滿足,例如一個(gè)程序sleep了酗捌,此時(shí)就處于等待態(tài)呢诬。

2.2 進(jìn)程的創(chuàng)建與使用

multiprocessing模塊是跨平臺(tái)版本的多進(jìn)程模塊,提供了一個(gè)Process類來(lái)代表一個(gè)進(jìn)程對(duì)象胖缤,這個(gè)對(duì)象可以理解為一個(gè)獨(dú)立的進(jìn)程尚镰,可以執(zhí)行另外的事情。

1哪廓、簡(jiǎn)單進(jìn)程跑起來(lái)

from multiprocessing import Process
from time import sleep,ctime


def my_process():
    while True:
        print('----new process')
        sleep(1)


if __name__ == '__main__':
    new_process = Process(target=my_process)
    new_process.start()
    while True:
        print('----running process')
        sleep(1)

結(jié)果:

----running process
----new process
----running process
----new process
----running process
----new process
----running process
  • 創(chuàng)建子進(jìn)程時(shí)狗唉,只需要傳入一個(gè)可執(zhí)行函數(shù)和對(duì)應(yīng)參數(shù),創(chuàng)建一個(gè)Process()實(shí)例涡真,用start()方法就可以了敞曹。

2、函數(shù)傳參及進(jìn)程id

from multiprocessing import Process
from time import sleep,ctime
import os


def my_process(pm, *args, **kwargs):
    print('----son process id is %s and parent is is %s' % (os.getpid(), os.getppid()))
    print(pm)
    for i, v in enumerate(args):
        print(i, v)
    for key, value in kwargs.items():
        print(key, value)
    sleep(1)


if __name__ == '__main__':
    new_process = Process(target=my_process, args=('first', 'second'), kwargs={'test': 'first'})
    new_process.start()
    print('----parent process id is %s ' % os.getpid())
    sleep(1)

結(jié)果:

----parent process id is 4292 
----son process id is 8656 and parent is is 4292
0 first
1 second
test first

可以看出综膀,創(chuàng)建出來(lái)的進(jìn)程是當(dāng)前運(yùn)行進(jìn)程的子進(jìn)程。

3局齿、Process詳解

Process的語(yǔ)法結(jié)構(gòu):

Process([group [, target [, name [, args [, kwargs]]]]])
  • target:如果函數(shù)傳遞了函數(shù)的引用剧劝,可以認(rèn)為創(chuàng)建的子進(jìn)程就執(zhí)行它的代碼。
  • args:給target傳遞參數(shù)抓歼,以元組的形式傳入讥此。
  • kwargs:給target指定的函數(shù)傳遞命名參數(shù),字典形式谣妻。
  • name:給程序設(shè)定一個(gè)名字萄喳,也可以不設(shè)定。
  • group:指定進(jìn)程組蹋半,大部分情況下用不到他巨。

Process的創(chuàng)建的實(shí)例對(duì)象的常用方法:

  • start():?jiǎn)?dòng)子進(jìn)程實(shí)例(創(chuàng)建子進(jìn)程)。
  • is_alive():判斷子進(jìn)程是否存活。
  • join():是否等待子進(jìn)程執(zhí)行結(jié)束或等待多少秒染突。
  • terminate():不管任務(wù)是否完成捻爷,強(qiáng)制終止子進(jìn)程。

Process()創(chuàng)建實(shí)例對(duì)象的常用屬性:

name:當(dāng)前進(jìn)程的別名份企,默認(rèn)為Process-N也榄,N為從1開(kāi)始遞增的整數(shù)。
pid:當(dāng)前進(jìn)程的pid司志。

4甜紫、進(jìn)程之間不共享全局變量

from multiprocessing import Process
from time import sleep,ctime
import os

global_list = [1, 2, 3, 4, 5]


def process_one(param):
    print('process_one pid=%s ' % os.getpid())
    param.append(6)
    print(param)


def process_two(param):
    print('process_two pid=%s ' % os.getpid())
    print(param)


if __name__ == '__main__':
    process_ob_one = Process(target=process_one, args=(global_list, ))
    process_ob_one.start()
    print(process_ob_one.name)
    sleep(2)
    proces_ob_two = Process(target=process_two, args=(global_list, ))
    proces_ob_two.start()
    print(proces_ob_two.name)

結(jié)果:

Process-1
process_one pid=8984 
[1, 2, 3, 4, 5, 6]
Process-2
process_two pid=9200 
[1, 2, 3, 4, 5]
  • 每個(gè)實(shí)例對(duì)象的name從1依次遞增。
  • 全局變量不共享骂远。

2.3 進(jìn)程與線程的區(qū)別

進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的獨(dú)立單位囚霸。線程是一個(gè)實(shí)體,是cpu調(diào)度和分配的基本單位吧史,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位邮辽。線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(如程序計(jì)算器贸营、一組寄存器和棧)吨述,但是它可以共享同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。

同時(shí)總結(jié)有以下幾個(gè)特點(diǎn)钞脂。

  • 一個(gè)程序至少有一個(gè)進(jìn)程揣云,一個(gè)進(jìn)程至少有一個(gè)線程,線程不能獨(dú)立執(zhí)行冰啃,必須依存在進(jìn)程中邓夕。
  • 線程的劃分尺度小于進(jìn)程(資源比進(jìn)程少),使得多線程的并發(fā)性高阎毅。
  • 進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元焚刚,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率扇调。
  • 線程的執(zhí)行開(kāi)銷小矿咕,不利于系統(tǒng)資源的管理和保護(hù),進(jìn)程相反狼钮。

2.4 進(jìn)程之間的通信

Process之間有時(shí)需要通信碳柱,操作系統(tǒng)提供了很多機(jī)制來(lái)實(shí)現(xiàn)進(jìn)程之間的通信。

1熬芜、Queen的使用

可以用multiprocessing模塊的Queen實(shí)現(xiàn)進(jìn)程之間的數(shù)據(jù)傳遞莲镣,Queen本身是一個(gè)消息隊(duì)列程序。

from multiprocessing import Queue


msg_queen = Queue(3)
msg_queen.put('first msg')
msg_queen.put('second msg')
msg_queen.put('third msg')
print(msg_queen.full())
try:
    msg_queen.put('fourth msg', True, 3)
except:
    print('msg is full, its size is %d' % msg_queen.qsize())

try:
    msg_queen.put_nowait('fourth msg')
except:
    print('msg is full, without timeout, its size is %d' % msg_queen.qsize())

if not msg_queen.full():
    msg_queen.put_nowait('先判斷再寫(xiě)入消息')
if not msg_queen.empty():
    for i in range(msg_queen.qsize()):
        # 先判斷再獲取
        print(msg_queen.get_nowait())

結(jié)果:

True
msg is full, its size is 3
msg is full, without timeout, its size is 3
first msg
second msg
third msg

2涎拉、Queen語(yǔ)法

初始化Queen對(duì)象時(shí)瑞侮,若括號(hào)沒(méi)有指定最大可接收消息數(shù)目或消息數(shù)目為負(fù)值的圆,那就代表可接收的消息數(shù)沒(méi)有上限(除非內(nèi)存使用完)。

常用方法介紹:

  • qsize():消息隊(duì)列的長(zhǎng)度区岗。
  • put(msg, block, timeout):試圖插入消息隊(duì)列略板,超時(shí)拋錯(cuò)。
  • block默認(rèn)為True慈缔,如果使用默認(rèn)值叮称,且沒(méi)有設(shè)置timeout,消息隊(duì)列已滿藐鹤,那么程序?qū)⑻幱谧枞麪顟B(tài)(停在等待寫(xiě)入狀態(tài))瓤檐,直到從消息隊(duì)列騰出空間為止。如果設(shè)置了timeout娱节,則會(huì)等待timeout秒挠蛉,若還沒(méi)獲取到空間就拋錯(cuò)。
  • put_nowait(msg):立即插入消息肄满,已滿報(bào)錯(cuò)谴古。put_nowait(item)==put(item, False);
  • full():消息隊(duì)列是否已滿稠歉。
  • empty():消息隊(duì)列是否為空掰担。
  • get(block, timeout):試圖獲取消息,超時(shí)拋錯(cuò)怒炸。
    • block默認(rèn)為True带饱,如果使用默認(rèn)值,且沒(méi)有設(shè)置timeout阅羹,消息隊(duì)列為空勺疼,那么程序?qū)⑻幱谧枞麪顟B(tài)(停在讀取狀態(tài)),直到從消息隊(duì)列中讀到消息為止捏鱼。如果設(shè)置了timeout执庐,則會(huì)等待timeout秒,若還沒(méi)獲取到就拋錯(cuò)导梆。
    • block為False轨淌,消息隊(duì)列為空,則會(huì)立即拋出異常问潭。
  • get_nowait(msg):立即獲取消息,異常報(bào)錯(cuò)婚被。get_nowait()==get(False)

簡(jiǎn)單跑跑狡忙。

from multiprocessing import Queue, Process
from time import sleep
import random

msg_queen = Queue(5)


def wq(msg_q):
    while True:
        if not msg_q.full():
            msg_q.put_nowait('put data')
            sleep(random.random())
            print('put data into queue')
        else:
            break


def rq(msg_q):
    while True:
        if not msg_q.empty():
            data = msg_q.get_nowait()
            sleep(random.random())
            print(data)
        else:
            break


if __name__ == '__main__':
    wq_process = Process(target=wq, args=(msg_queen, ))
    wq_process.start()
    sleep(random.random())
    # wq_process.join()
    rq_process = Process(target=rq, args=(msg_queen, ))
    rq_process.start()
    # rq_process.join()

2.5 進(jìn)程池

當(dāng)需要?jiǎng)?chuàng)建的子進(jìn)程數(shù)量不多的時(shí)候,可以直接利用multiprocessing中的Process去動(dòng)態(tài)創(chuàng)建多個(gè)進(jìn)程址芯,但是如果要?jiǎng)?chuàng)建成百上千個(gè)目標(biāo)灾茁,手動(dòng)去創(chuàng)建的進(jìn)程的工作量較大窜觉,此時(shí)就可以用到multiprocessing模塊提供的Pool方法。

初始化進(jìn)程池北专,可以制定一個(gè)最大進(jìn)程數(shù)禀挫,當(dāng)有新的請(qǐng)求提交到Pool中時(shí),如果池還沒(méi)滿拓颓,就會(huì)創(chuàng)建一個(gè)新的進(jìn)程來(lái)執(zhí)行這個(gè)請(qǐng)求语婴;如果進(jìn)程池的數(shù)目已經(jīng)達(dá)到最大值,那么該請(qǐng)求就會(huì)等待驶睦,指導(dǎo)進(jìn)程池中的進(jìn)程結(jié)束砰左,才會(huì)用之前的進(jìn)程來(lái)執(zhí)行新的任務(wù)。

from multiprocessing import Pool
import time
import random
import os


def work_pool(msg):
    t_start = time.time()
    print('this num of the process is %s' % msg)
    print('this pid of the process is %s' % os.getpid())
    print('this ppid of the process is %s' % os.getppid())
    time.sleep(random.random()*10)
    t_stop = time.time()
    print('spend time %d' % int(t_stop-t_start))


if __name__ == '__main__':
    process_pool = Pool(5)
    for i in range(10):
        process_pool.apply_async(work_pool, (i, ))
    process_pool.close()
    process_pool.join()

結(jié)果:

this num of the process is 0
this pid of the process is 8144
this ppid of the process is 6396
this num of the process is 1
this pid of the process is 8596
this ppid of the process is 6396
this num of the process is 2
this pid of the process is 6464
this ppid of the process is 6396
this num of the process is 3
this pid of the process is 6240
this ppid of the process is 6396
this num of the process is 4
this pid of the process is 7300
this ppid of the process is 6396
spend time 3
this num of the process is 5
this pid of the process is 8596
this ppid of the process is 6396
spend time 3
this num of the process is 6
this pid of the process is 7300
this ppid of the process is 6396
spend time 6
this num of the process is 7
this pid of the process is 8144
this ppid of the process is 6396
spend time 2
this num of the process is 8
this pid of the process is 7300
this ppid of the process is 6396
spend time 7
this num of the process is 9
this pid of the process is 6464
this ppid of the process is 6396
spend time 4
spend time 9
spend time 3
spend time 6
spend time 7

可以看出是重復(fù)利用進(jìn)程的场航,且進(jìn)程之間互不影響缠导。

Pool常用函數(shù)解析:

  • apply_async(func[, args[, kwargs]]):非阻塞方式調(diào)用func(并行執(zhí)行,阻塞方式必須等待上一個(gè)進(jìn)程完了才能執(zhí)行下一個(gè)進(jìn)程)溉痢,args為參數(shù)列表僻造,元組形式;kwargs是命名參數(shù)字典孩饼。
  • close():關(guān)閉進(jìn)程池髓削,不再接受其它任務(wù)。
  • terminate():不管任務(wù)是否完成捣辆,立即終止蔬螟。
  • join():主進(jìn)程阻塞,等待子進(jìn)程結(jié)束退出汽畴,必須在close或terminate之后才能調(diào)用旧巾。

進(jìn)程池中的Queue:

如果要使用Pool創(chuàng)建進(jìn)程,就需要使用mutilprocessing.Manage().Queue()忍些,而不是multiprocessing.Queue()鲁猩,否則會(huì)得到一條如下的錯(cuò)誤信息:

RuntimeError: Queue objects should only be shared between processes through inheritance.

下面的實(shí)例演示了進(jìn)程池中的進(jìn)程如何通信:

from multiprocessing import Pool, Manager
import time
import random
import os


def write_p(msg):
    for i in 'daocoder':
        msg.put(i)
        print('process id is %s' % os.getpid())
        print('parent process id is %s' % os.getppid())
    time.sleep(random.random())


def read_p(q):
    for i in range(q.qsize()):
        char_name = q.get()
        print(char_name)
        print('process id is %s' % os.getpid())
        print('parent process id is %s' % os.getppid())
    time.sleep(random.random())


if __name__ == '__main__':
    q = Manager().Queue()
    process_pool = Pool()
    process_pool.apply_async(write_p, (q, ))
    time.sleep(random.random())
    process_pool.apply_async(read_p, (q, ))
    process_pool.close()
    process_pool.join()

結(jié)果:

process id is 6532
parent process id is 7968
process id is 6532
parent process id is 7968
process id is 6532
parent process id is 7968
process id is 6532
parent process id is 7968
process id is 6532
parent process id is 7968
process id is 6532
parent process id is 7968
process id is 6532
parent process id is 7968
process id is 6532
parent process id is 7968
d
process id is 4436
parent process id is 7968
a
process id is 4436
parent process id is 7968
o
process id is 4436
parent process id is 7968
c
process id is 4436
parent process id is 7968
o
process id is 4436
parent process id is 7968
d
process id is 4436
parent process id is 7968
e
process id is 4436
parent process id is 7968
r
process id is 4436
parent process id is 7968

3、協(xié)程

3.1 迭代器

迭代是一種訪問(wèn)集合方式的一種方式罢坝。迭代器是可以記住其遍歷對(duì)象的位置的對(duì)象廓握。迭代器對(duì)象從集合的第一個(gè)元素開(kāi)始訪問(wèn),直到所有的元素被訪問(wèn)完結(jié)束嘁酿,迭代器只能前進(jìn)不能后退隙券。

3.1.1 可迭代對(duì)象

在py中,有我們已知的list闹司、tuple娱仔、str等類型的數(shù)據(jù)使用for…in…的循環(huán)語(yǔ)法從其中拿到數(shù)據(jù)進(jìn)行使用,我們稱這樣的過(guò)程為遍歷游桩、也叫迭代牲迫。

那么問(wèn)題來(lái)了耐朴,為什么有的數(shù)據(jù)類型可以用for…in…進(jìn)行迭代,而有的數(shù)據(jù)不行盹憎。

for i in 100:
    print(i)
Traceback (most recent call last):
  File "C:\software\python\lib\site-packages\IPython\core\interactiveshell.py", line 2910, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-2-bcddcd506fd8>", line 1, in <module>
    for i in 100:
TypeError: 'int' object is not iterable

整型對(duì)象不是可迭代的筛峭。

from collections import Iterable


class Mylist(object):
    def __init__(self):
        self.container = []

    def add(self, element):
        self.container.append(element)


mylist = Mylist()
mylist.add(1)
mylist.add(2)
mylist.add(3)

for i in mylist:
    print(i)

Traceback (most recent call last):
  File "D:/python/demo/thread.py", line 22, in <module>
    for i in mylist:
TypeError: 'Mylist' object is not iterable

我們自定義一個(gè)容器類型,里面存放了3個(gè)數(shù)據(jù)陪每,嘗試去遍歷它影晓,發(fā)現(xiàn)報(bào)錯(cuò)。即這個(gè)自定義對(duì)象也是不可以遍歷的奶稠。那么如何判斷一個(gè)對(duì)象是否是可以遍歷的俯艰?

3.1.2 判斷對(duì)象是否可以遍歷(迭代)

我們可以通過(guò)for…in…這類語(yǔ)句迭代讀取一條數(shù)據(jù)的對(duì)象稱之為可迭代對(duì)象(Iterable)。

可以使用instance()判斷一個(gè)對(duì)象是否是Iterable對(duì)象锌订。

以上面的mylist為例竹握。

print(isinstance(mylist, Iterable))
print(isinstance([], Iterable))
print(isinstance({}, Iterable))
print(isinstance(100, Iterable))
print(isinstance('daocoder', Iterable))

結(jié)果:

False
True
True
False
True

3.1.3 可迭代對(duì)象的本質(zhì)

我們從表面去看,對(duì)可迭代對(duì)象來(lái)說(shuō)辆飘,每迭代一次都會(huì)返回下一個(gè)元素啦辐,直到這個(gè)對(duì)象遍歷完畢。那么在這個(gè)過(guò)程中就應(yīng)該有一個(gè)“變量”在記錄每次迭代后當(dāng)前的位置蜈项,以便下次都可以獲取到下一條數(shù)據(jù)芹关。我們稱這個(gè)變量為迭代器(iterator)。

可迭代對(duì)象的本質(zhì)就是可以向我們提供一個(gè)這樣的“變量”紧卒,即迭代器幫助我們?nèi)ミM(jìn)行迭代遍歷使用侥衬。

可迭代對(duì)象通過(guò)iter()方法向我們提供一個(gè)迭代器,我們?cè)诘粋€(gè)可迭代對(duì)象時(shí)跑芳,實(shí)際上就是獲取該對(duì)象提供的迭代器轴总,然后通過(guò)這個(gè)迭代器依次獲取該對(duì)象的每個(gè)數(shù)據(jù)。

那么就可以這么說(shuō)博个,一個(gè)具備iter()方法的對(duì)象就是可迭代對(duì)象怀樟。

還是上面mylist那個(gè)自定義對(duì)象,添加如下代碼:

def __iter__(self):
    pass

運(yùn)行:

print(isinstance(mylist, Iterable))

結(jié)果:

True

那么按道理來(lái)說(shuō)盆佣,mylist就可以遍歷了才是往堡,如下:

for i in mylist:
    print(i)

結(jié)果:

Traceback (most recent call last):
True
  File "D:/python/demo/thread.py", line 24, in <module>
    for i in mylist:
TypeError: iter() returned non-iterator of type 'NoneType'

3.1.4 iter()和next()函數(shù)

list、tuple等都是可迭代對(duì)象共耍,我們可以通過(guò)iter()函數(shù)獲取這些可迭代對(duì)象的迭代器虑灰,然后我們可以通過(guò)獲取迭代器的next()函數(shù)來(lái)獲取下一個(gè)數(shù)據(jù)。iter()函數(shù)實(shí)際上就是調(diào)用了可迭代對(duì)象的iter方法痹兜。

test = [1, 2, 3, 4, 5]
list_iter = iter(test)
print(next(list_iter))
print(next(list_iter))
print(next(list_iter))
print(next(list_iter))
print(next(list_iter))
print(next(list_iter))

結(jié)果:

1
2
3
4
5
Traceback (most recent call last):
  File "D:/python/demo/thread.py", line 34, in <module>
    print(next(list_iter))
StopIteration

當(dāng)我們迭代完最后一個(gè)數(shù)據(jù)之后穆咐,再次調(diào)用next()函數(shù)就會(huì)拋出StopIteration的異常,說(shuō)明我們所有的數(shù)據(jù)都已經(jīng)迭代完成佃蚜,不需要再執(zhí)行next()函數(shù)了庸娱。

3.1.5 如何判斷一個(gè)對(duì)象是否是迭代器

可以使用isinstance()判斷一個(gè)對(duì)象是否是Iterator對(duì)象。

print(isinstance('daocoder', Iterator))
print(isinstance(iter('daocoder'), Iterator))
print(isinstance(123, Iterator))
print(isinstance(iter(123), Iterator))

結(jié)果:

False
True
False
Traceback (most recent call last):
  File "D:/python/demo/thread.py", line 30, in <module>
    print(isinstance(iter(123), Iterator))
TypeError: 'int' object is not iterable

3.1.6 迭代器Iterator

從上面的一步步走過(guò)來(lái)谐算,我們清楚迭代器是幫我們記錄每次迭代到訪問(wèn)的位置熟尉,當(dāng)我們對(duì)迭代器使用next()函數(shù)的時(shí)候,迭代器會(huì)向我們返回記錄它的下一個(gè)數(shù)據(jù)的位置洲脂。實(shí)際上斤儿,使用next()函數(shù)的時(shí)候,其調(diào)用的就是迭代器對(duì)象的next方法恐锦。但這還不夠往果,python要求迭代器本身也是可迭代的,所以還要為迭代器實(shí)現(xiàn)iter方法一铅,而iter要返回一個(gè)迭代器陕贮,迭代器自身就是一個(gè)迭代器,所以返回其自身就好潘飘。

簡(jiǎn)而言之肮之,一個(gè)實(shí)現(xiàn)了iternext方法的對(duì)象就是迭代器。

然后上面的自定義可以寫(xiě)成這樣卜录。

class Mylist(object):
    def __init__(self):
        self.container = []

    def add(self, element):
        self.container.append(element)

    def __iter__(self):
        return MyIterator(self)


class MyIterator(object):
    def __init__(self, outlist):
        self.mylist = outlist
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current < len(self.mylist.container):
            item = self.mylist.container[self.current]
            self.current += 1
            return item
        else:
            raise StopIteration


if __name__ == '__main__':
    mylist = Mylist()
    mylist.add(1)
    mylist.add(2)
    mylist.add(3)
    mylist.add(4)
    mylist.add(5)
    for num in mylist:
        print(num)

3.1.7 for…in…循環(huán)的本質(zhì)

for item in iterable 循環(huán)的本質(zhì)就是先通過(guò)iter()獲取可迭代對(duì)象Iterable的迭代器戈擒,然后不斷調(diào)用next()方法來(lái)獲取下一個(gè)值并將其復(fù)制給item,當(dāng)遇到StopIteration的異常后循環(huán)結(jié)束艰毒。

3.1.8 迭代器的應(yīng)用場(chǎng)景

迭代器的核心功能是可以通過(guò)next()函數(shù)的調(diào)用來(lái)返回下一個(gè)數(shù)據(jù)值筐高。如果每次返回的數(shù)據(jù)值不是在已有數(shù)據(jù)集合中讀取的,而是通過(guò)一定的規(guī)律生成的丑瞧,那么也就意味著可以不用再依賴一個(gè)已有的數(shù)據(jù)集合柑土,即不用再將所有需要迭代的數(shù)據(jù)一次性緩存下來(lái)供后續(xù)依次讀取,這樣就可以節(jié)省大量?jī)?nèi)存嗦篱。

下面利用迭代器實(shí)現(xiàn)兔子數(shù)列(斐波那契數(shù)列)冰单。

class FibIterator(object):
    def __init__(self, n):
        self.n = n
        self.current = 0
        self.num1 = 0
        self.num2 = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.current < self.n:
            num = self.num1
            self.num1, self.num2 = self.num2, self.num1+self.num2
            self.current += 1
            return num
        else:
            raise StopIteration


if __name__ == '__main__':
    fib = FibIterator(5)
    for i in range(6):
        print(next(fib))

結(jié)果為:

0
1
1
2
3
Traceback (most recent call last):
  File "D:/python/demo/thread.py", line 29, in <module>
    print(next(fib))
  File "D:/python/demo/thread.py", line 23, in __next__
    raise StopIteration
StopIteration

3.2 生成器

利用迭代器,我們可以在每次迭代獲取數(shù)據(jù)(利用迭代器的next方法)時(shí)按照特定的規(guī)律進(jìn)行生成灸促。但我們?cè)趯?shí)現(xiàn)一個(gè)迭代器時(shí)诫欠,關(guān)于當(dāng)前迭代的狀態(tài)需要我們自己去記錄,進(jìn)而才能依據(jù)當(dāng)前狀態(tài)去生成下一個(gè)數(shù)據(jù)浴栽。為了達(dá)到記錄當(dāng)前狀態(tài)荒叼,并配合next()函數(shù)進(jìn)行迭代使用,我們可以采用更簡(jiǎn)潔的用法典鸡,即生成器generator()被廓。

3.2.1 創(chuàng)建生成器

1、利用生成器符號(hào)

列表生成式的[]改成()萝玷。

L = [i for i in range(6)]
print(L)

G = (i for i in range(6))
print(G)

結(jié)果:

[0, 1, 2, 3, 4, 5]
<generator object <genexpr> at 0x0000000001E98308>

創(chuàng)建L和G的區(qū)別僅在于最外層的()和[]嫁乘,L是一個(gè)列表昆婿,G是一個(gè)生成器。我們可以直接打印L的每個(gè)元素蜓斧,對(duì)于生成器G仓蛆,我們可以按照迭代器的使用方法來(lái)使用,即可以通過(guò)next()函數(shù)挎春、for循環(huán)看疙、list()等方法使用。

L = [i for i in range(6)]
print(L)

G = (i for i in range(6))
print(G)

# print(next(G))
# print(next(G))
# print(next(G))
# print(next(G))
# print(next(G))
# print(next(G))

for j in G:
    print(j)

結(jié)果為:

[0, 1, 2, 3, 4, 5]
<generator object <genexpr> at 0x0000000001F08308>
0
1
2
3
4
5

2直奋、yield

generator非常強(qiáng)大能庆。如果推算的算法比較復(fù)雜,用類似列表生成式的for循環(huán)無(wú)法實(shí)現(xiàn)的時(shí)候脚线,還可以用函數(shù)來(lái)實(shí)現(xiàn)搁胆。

def fib(n):
    current = 0
    num1 = 0
    num2 = 1
    while current < n:
        current += 1
        num = num1
        num1, num2 = num2, num1 + num2
        yield num
    return 'done'


for i in fib(6):
    print(i)

結(jié)果:

0
1
1
2
3
5

在使用生成器實(shí)現(xiàn)的方式中,我們將原本在迭代器中next方法中實(shí)現(xiàn)的基本邏輯放到一個(gè)函數(shù)中實(shí)現(xiàn)邮绿,現(xiàn)在將每次迭代器返回?cái)?shù)值的return換成了yield丰涉,此時(shí)定義的函數(shù)就不再是函數(shù),而是一個(gè)生成器斯碌。簡(jiǎn)單來(lái)說(shuō):只要有yield關(guān)鍵字的就稱為生成器一死。

但是用for循環(huán)調(diào)用generator時(shí),發(fā)現(xiàn)拿不到generator的return語(yǔ)句的返回值傻唾。如果想獲取返回值投慈,必須捕獲StopIteration錯(cuò)誤,返回值包含在StopIteration的value中冠骄。

def fib(n):
    current = 0
    num1 = 0
    num2 = 1
    while current < n:
        current += 1
        num = num1
        num1, num2 = num2, num1 + num2
        yield num
    return 'done'


G = fib(5)
while True:
    try:
        n = next(G)
        print(n)
    except StopIteration as se:
        print(se.value)
        break

結(jié)果:

0
1
1
2
3
done

總結(jié):

  • 試用了yield關(guān)鍵字的函數(shù)就不再是函數(shù)伪煤,而是生成器。
  • yield作用有兩點(diǎn):保存當(dāng)前的運(yùn)行狀態(tài)(斷點(diǎn))凛辣,然后暫停執(zhí)行抱既,即生成器被掛起;將yield關(guān)鍵字后面表達(dá)式的值作為返回值返回扁誓,此時(shí)可以理解為起到了return作用防泵。
  • 使用next()函數(shù)可以生成器從斷點(diǎn)處繼續(xù)執(zhí)行,即喚醒生成器(函數(shù))蝗敢。
  • py3中的生成器可以使用return返回最終運(yùn)行的返回值捷泞,而py2中的生成器不允許使用個(gè)return返回一個(gè)返回值(可以使用return語(yǔ)句從生成器中退出,但是不能return后面不能有任何表達(dá)式)寿谴。

3.2.2 使用send喚醒

除了上面利用next()函數(shù)來(lái)喚醒生成器繼續(xù)執(zhí)行外锁右,還可以使用send()函數(shù)來(lái)喚醒其繼續(xù)執(zhí)行。使用send()函數(shù)的另一個(gè)好處是可以同時(shí)向斷點(diǎn)處附加一個(gè)數(shù)據(jù)。

def fib(n):
    i = 0
    while i < n:
        temp = yield i
        print(temp)
        i += 1


G = fib(5)

print(next(G))
print(G.__next__())
print(G.send('daocoder'))
print(next(G))

結(jié)果:

0
None
1
daocoder
2
None
3

執(zhí)行到y(tǒng)ield時(shí)咏瑟,G函數(shù)暫存當(dāng)前狀態(tài)拂到,返回i的值;temp接收下次G.send('daocoder')码泞,next(G)===G.next()===G.send(None)谆焊。

3.3 協(xié)程

協(xié)程又稱為微線程,纖程浦夷,英文為coroutine。

3.3.1 協(xié)程簡(jiǎn)介

協(xié)程是py中實(shí)現(xiàn)多任務(wù)的另一種方式辜王,只不過(guò)比線程占用更小的資源劈狐,且切換上線文更快。

通俗的理解就是:在一個(gè)線程的某個(gè)函數(shù)呐馆,可以在任何地方保存當(dāng)前函數(shù)的一些臨時(shí)變量等信息肥缔,談后切換到另一個(gè)函數(shù)中執(zhí)行,注意這里不是通過(guò)調(diào)用函數(shù)的方式去做到的汹来,并且切換的次數(shù)以及什么時(shí)候切換到原來(lái)的函數(shù)都由開(kāi)發(fā)者決定续膳。

3.3.2 yield

import time


def work1():
    while True:
        print('work1')
        yield
        time.sleep(0.5)


def work2():
    while True:
        print('work2')
        yield
        time.sleep(0.5)


if __name__ == '__main__':
    w1 = work1()
    w2 = work2()
    while True:
        next(w1)
        next(w2)

結(jié)果為:

work1
work2
work1
work2
work1
work2

3.3.3 greenlet

為了更好的使用協(xié)程來(lái)完成任務(wù),py提供了greenlet對(duì)其封裝收班,從而使切換任務(wù)變得更加簡(jiǎn)單坟岔。

import time
from greenlet import greenlet


def work1():
    while True:
        print('work1')
        w2.switch()
        time.sleep(0.5)


def work2():
    while True:
        print('work2')
        w1.switch()
        time.sleep(0.5)


if __name__ == '__main__':
    w1 = greenlet(work1)
    w2 = greenlet(work2)
    w1.switch()

結(jié)果為:

work1
work2
work1
work2
work1
work2

3.3.3 gevent

greenlet已經(jīng)實(shí)現(xiàn)了協(xié)程,但是如上面代碼所示摔桦,它還需要人工進(jìn)行切換社付,由此py提供了一個(gè)更強(qiáng)大的封裝gevent,它能夠自動(dòng)切換任務(wù)邻耕。

其原理是當(dāng)一個(gè)greenlet遇到一個(gè)IO(網(wǎng)絡(luò)鸥咖、文件等輸入輸出操作)時(shí),就自動(dòng)切換到其它的greenlet兄世,等到IO操作完成啼辣,再在適當(dāng)?shù)臈l件下切回來(lái)繼續(xù)執(zhí)行。

由于IO操作非常耗時(shí)御滩,經(jīng)常使程序處于等待狀態(tài)鸥拧,有了gevent為我們自動(dòng)切換協(xié)程,就保證總有g(shù)reenlet在運(yùn)行削解,而不是等待IO住涉。

1、無(wú)IO操作等待

import gevent


def work(n):
    for i in range(n):
        print('num is %s, current greenlet is %s' % (i, gevent.getcurrent()))


g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 4)
g3 = gevent.spawn(work, 3)
g1.join()
g2.join()
g3.join()

結(jié)果是:

num is 0, current greenlet is <Greenlet at 0x2bf6898: work(5)>
num is 1, current greenlet is <Greenlet at 0x2bf6898: work(5)>
num is 2, current greenlet is <Greenlet at 0x2bf6898: work(5)>
num is 3, current greenlet is <Greenlet at 0x2bf6898: work(5)>
num is 4, current greenlet is <Greenlet at 0x2bf6898: work(5)>
num is 0, current greenlet is <Greenlet at 0x2bf69c8: work(4)>
num is 1, current greenlet is <Greenlet at 0x2bf69c8: work(4)>
num is 2, current greenlet is <Greenlet at 0x2bf69c8: work(4)>
num is 3, current greenlet is <Greenlet at 0x2bf69c8: work(4)>
num is 0, current greenlet is <Greenlet at 0x2bf6a60: work(3)>
num is 1, current greenlet is <Greenlet at 0x2bf6a60: work(3)>
num is 2, current greenlet is <Greenlet at 0x2bf6a60: work(3)>

可以看出3個(gè)greenlet是依次執(zhí)行而不是順序執(zhí)行钠绍。

2舆声、有IO操作等待(gevent.sleep)

import gevent


def work(n):
    for i in range(n):
        print('num is %s, current greenlet is %s' % (i, gevent.getcurrent()))
        gevent.sleep(0.5)


g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 4)
g3 = gevent.spawn(work, 3)
g1.join()
g2.join()
g3.join()

結(jié)果:

num is 0, current greenlet is <Greenlet at 0x2bc5898: work(5)>
num is 0, current greenlet is <Greenlet at 0x2bc59c8: work(4)>
num is 0, current greenlet is <Greenlet at 0x2bc5a60: work(3)>
num is 1, current greenlet is <Greenlet at 0x2bc5898: work(5)>
num is 1, current greenlet is <Greenlet at 0x2bc5a60: work(3)>
num is 1, current greenlet is <Greenlet at 0x2bc59c8: work(4)>
num is 2, current greenlet is <Greenlet at 0x2bc5898: work(5)>
num is 2, current greenlet is <Greenlet at 0x2bc59c8: work(4)>
num is 2, current greenlet is <Greenlet at 0x2bc5a60: work(3)>
num is 3, current greenlet is <Greenlet at 0x2bc5898: work(5)>
num is 3, current greenlet is <Greenlet at 0x2bc59c8: work(4)>
num is 4, current greenlet is <Greenlet at 0x2bc5898: work(5)>

這里發(fā)現(xiàn)greenlet遇到其耗時(shí)操作時(shí),自動(dòng)切換協(xié)程。

3媳握、有IO操作等待(time.sleep)

import gevent
import time


def work(n):
    for i in range(n):
        print('num is %s, current greenlet is %s' % (i, gevent.getcurrent()))
        time.sleep(0.5)


g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 4)
g3 = gevent.spawn(work, 3)
g1.join()
g2.join()
g3.join()

結(jié)果:

num is 0, current greenlet is <Greenlet at 0x2bc5898: work(5)>
num is 1, current greenlet is <Greenlet at 0x2bc5898: work(5)>
num is 2, current greenlet is <Greenlet at 0x2bc5898: work(5)>
num is 3, current greenlet is <Greenlet at 0x2bc5898: work(5)>
num is 4, current greenlet is <Greenlet at 0x2bc5898: work(5)>
num is 0, current greenlet is <Greenlet at 0x2bc59c8: work(4)>
num is 1, current greenlet is <Greenlet at 0x2bc59c8: work(4)>
num is 2, current greenlet is <Greenlet at 0x2bc59c8: work(4)>
num is 3, current greenlet is <Greenlet at 0x2bc59c8: work(4)>
num is 0, current greenlet is <Greenlet at 0x2bc5a60: work(3)>
num is 1, current greenlet is <Greenlet at 0x2bc5a60: work(3)>
num is 2, current greenlet is <Greenlet at 0x2bc5a60: work(3)>

這里我們發(fā)現(xiàn)碱屁,time模塊的耗時(shí)并沒(méi)有使其自動(dòng)切換上下文。

4蛾找、有IO操作等待(利用monkey)

import gevent
from gevent import monkey
import time


monkey.patch_all()


def work(n, *args):
    for i in range(n):
        print('name is %s, current greenlet is %s' % (args[0], gevent.getcurrent()))
        time.sleep(0.5)


g1 = gevent.spawn(work, 5, 'g1')
g2 = gevent.spawn(work, 4, 'g2')
g3 = gevent.spawn(work, 3, 'g3')
g1.join()
g2.join()
g3.join()

結(jié)果:

num is 0, current greenlet is <Greenlet at 0x30590e0: work(5)>
num is 0, current greenlet is <Greenlet at 0x3059210: work(4)>
num is 0, current greenlet is <Greenlet at 0x30592a8: work(3)>
num is 1, current greenlet is <Greenlet at 0x30590e0: work(5)>
num is 1, current greenlet is <Greenlet at 0x30592a8: work(3)>
num is 1, current greenlet is <Greenlet at 0x3059210: work(4)>
num is 2, current greenlet is <Greenlet at 0x30590e0: work(5)>
num is 2, current greenlet is <Greenlet at 0x3059210: work(4)>
num is 2, current greenlet is <Greenlet at 0x30592a8: work(3)>
num is 3, current greenlet is <Greenlet at 0x30590e0: work(5)>
num is 3, current greenlet is <Greenlet at 0x3059210: work(4)>
num is 4, current greenlet is <Greenlet at 0x30590e0: work(5)>

另一種寫(xiě)法:

import gevent
from gevent import monkey
import time


monkey.patch_all()


def work(n, *args):
    for i in range(n):
        print('name is %s, current greenlet is %s' % (args[0], gevent.getcurrent()))
        time.sleep(0.5)


gevent.joinall([
    gevent.spawn(work, 5, 'g1'),
    gevent.spawn(work, 4, 'g2'),
    gevent.spawn(work, 3, 'g3')
])

結(jié)果為:

name is g1, current greenlet is <Greenlet at 0x30680e0: work(5, 'g1')>
name is g2, current greenlet is <Greenlet at 0x3068210: work(4, 'g2')>
name is g3, current greenlet is <Greenlet at 0x30682a8: work(3, 'g3')>
name is g1, current greenlet is <Greenlet at 0x30680e0: work(5, 'g1')>
name is g3, current greenlet is <Greenlet at 0x30682a8: work(3, 'g3')>
name is g2, current greenlet is <Greenlet at 0x3068210: work(4, 'g2')>
name is g1, current greenlet is <Greenlet at 0x30680e0: work(5, 'g1')>
name is g2, current greenlet is <Greenlet at 0x3068210: work(4, 'g2')>
name is g3, current greenlet is <Greenlet at 0x30682a8: work(3, 'g3')>
name is g1, current greenlet is <Greenlet at 0x30680e0: work(5, 'g1')>
name is g2, current greenlet is <Greenlet at 0x3068210: work(4, 'g2')>
name is g1, current greenlet is <Greenlet at 0x30680e0: work(5, 'g1')>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末娩脾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子打毛,更是在濱河造成了極大的恐慌柿赊,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幻枉,死亡現(xiàn)場(chǎng)離奇詭異碰声,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)熬甫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門胰挑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人椿肩,你說(shuō)我怎么就攤上這事瞻颂。” “怎么了郑象?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵贡这,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我厂榛,道長(zhǎng)藕坯,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任噪沙,我火速辦了婚禮炼彪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘正歼。我一直安慰自己辐马,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布局义。 她就那樣靜靜地躺著喜爷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪萄唇。 梳的紋絲不亂的頭發(fā)上檩帐,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音另萤,去河邊找鬼湃密。 笑死诅挑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泛源。 我是一名探鬼主播拔妥,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼达箍!你這毒婦竟也來(lái)了没龙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缎玫,失蹤者是張志新(化名)和其女友劉穎硬纤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體赃磨,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筝家,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煞躬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逸邦,死狀恐怖恩沛,靈堂內(nèi)的尸體忽然破棺而出仔戈,到底是詐尸還是另有隱情纵朋,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布牺堰,位于F島的核電站桥狡,受9級(jí)特大地震影響搅裙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裹芝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一部逮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫂易,春花似錦兄朋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至缕允,卻和暖如春峡扩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背障本。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工教届, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓巍佑,卻偏偏與公主長(zhǎng)得像茴迁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子萤衰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容