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

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

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

  • 有一個老板想開一個工廠生產手機。
  • 他需要花一些財力、物力及人力去制作一條生產線,這條線上所有的錢、人、物料準備:為了生產手機的儲備資源稱之為進程。
  • 有了生產線,老板負責把資源統(tǒng)一管理調度起來,然后還需要一個工人按照生產手機的工藝、去按部就班地把手機做出來。這個做事情的工人就叫線程。
  • 然后生產線可以運作起來了,但是效率低,手機供不應求。為了提高生產效率,老板又想了幾個辦法;
    • 在這條生產線上多招幾個工人,一起來做手機,這樣效率就增加了。即單進程、多線程模式。
    • 之后老板又發(fā)現(xiàn),單條生產線的工人并不是越多越好,因為一條生產線的資源、空間等有限,所以老板又花了財力物力去置辦了另外一條生產線,然后在招一些工人,效率又上去了。即多進程、多線程模式。
    • 現(xiàn)在已經有個多條生產線和多個工人(多進程、每個進程對應多條線程)。但是老板發(fā)現(xiàn),有時工人按照生產手機的工藝工作時,有時是為了等待上一道工序的完成是閑下來的,就侃大山去了。資本家么,就想了一個辦法:如果某個員工在工作時臨時沒事或者在等待另一個工人生產完謀道工序之后他才能再次工作,那么這個員工就利用這個時間去做其它的事情。那么也就是說:如果一個線程等待某些條件,可以充分利用這個時間去做其它事情。即協(xié)程

這里再總結術語有下面幾點:

  • 進程是資源分配和調度資源的最小單位。
  • 線程是進程的實體,是系統(tǒng)分配和調度的基本單位。
  • 進程切換需要的資源很最大,效率很低。
  • 線程切換需要的資源一般,效率一般(當然了在不考慮GIL的情況下)。
  • 協(xié)程切換任務資源很小,效率高。
  • 多進程、多線程根據(jù)cpu核數(shù)不一樣可能是并行的,但是協(xié)程是在一個線程中,所以是并發(fā)。

1、多任務的需求

在演唱會中,很多歌手都是歌舞一起表演,那加入用程序來模擬的話,就大概是這樣。

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()

結果:

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

上面運行的程序并沒有完成唱歌和跳舞同時進行的要求,那如果想要實現(xiàn)“唱歌跳舞”同時進行,那么就需要一個新的方方法:多任務。

那什么叫多任務。簡而言之就是系統(tǒng)可以同時運行多個任務。比如你聊天的時候可以聽歌,還可以瀏覽網頁等。

要實現(xiàn)它有很多方式,以前是單核cpu,由于cpu執(zhí)行代碼都是順序執(zhí)行的,那么單核cpu就輪流讓各個任務交替執(zhí)行,這樣反復執(zhí)行下去。表面上看,每個任務都是交替執(zhí)行的,但是,由于cpu的執(zhí)行速度比較快,我們感覺就像所有任務都在同時執(zhí)行一樣。

真正的并行執(zhí)行多任務只能在多核cpu上實現(xiàn),但是由于任務數(shù)量遠遠多于cpu的核心數(shù)量,所以系統(tǒng)也會自動把很多任務輪流調度到每個核心上執(zhí)行。

這里涉及到兩個概念。

  • 并發(fā):任務數(shù)多于cpu核數(shù),通過操作系統(tǒng)的各種任務調度算法,實現(xiàn)用多個任務“一起”執(zhí)行(實際上一些任務不在執(zhí)行,因為切換任務的速度相當快,看上去一起執(zhí)行而已)。
  • 并行:任務數(shù)小于等于cpu核數(shù),即任務真的是一起執(zhí)行的。

2、線程

python的thread模塊是比較底層的模塊,python的threading模塊是對thread做了一些封裝,可以更加方便的被使用。

2.1 使用threading模塊

1、單線程執(zhí)行一個任務

from time import sleep


def study_apologise():
    print('鋼鐵直男是要學會道歉的。')
    sleep(1)


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

結果就不少于5s學習時間。

2、多線程執(zhí)行一個任務

import threading
from time import sleep


def study_apologise():
    print('鋼鐵直男是要學會道歉的。')
    sleep(1)


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

結果就秒出了,可以明顯看出使用了多線程并發(fā)的操作,花費時間要短很多。且當調用start()時,才會真正的創(chuàng)建線程,并且開始執(zhí)行。

3、主線程會等待所有的子線程結束后才結束

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('---結束---')

這里主進程會一直向下進行,子進程代碼繼續(xù)運行,當所有子進程結束后,主進程才會結束。

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('當前的進程數(shù)為 %d' % thread_count)
        if thread_count <= 1:
            break
        sleep(0.5)

結果為:

我在唱歌 0
我在跳舞 0
當前的進程數(shù)為 3
當前的進程數(shù)為 3
當前的進程數(shù)為 3
我在跳舞 1
我在唱歌 1
當前的進程數(shù)為 3
我在唱歌 2
我在跳舞 2
當前的進程數(shù)為 3
當前的進程數(shù)為 3
當前的進程數(shù)為 2
當前的進程數(shù)為 1

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

在上面可以體現(xiàn)出,通過threading模塊能完成多任務的程序開發(fā)。為了讓每個線程封裝的更完美,在實際使用threading模塊時,通常會定義一個新的子類,這個子類繼承threading.Thread,然后重寫它的run方法,上面的這個例子就可以寫成下面這樣。

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('當前的進程數(shù)為 %d' % thread_count)
        if thread_count <= 1:
            break
        sleep(0.5)

結果是一樣的。

再一個小demo。

from time import sleep
import threading


class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            # name屬性中保存的是當前線程的名字
            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類中有一個run方法,用于定義線程的功能函數(shù),可以在自己的線程類中覆蓋該方法。創(chuàng)建自己的線程實例對象后,可以通過Tread的start方法,可以啟動該線程。當線程獲得執(zhí)行的機會時,就會調用run方法。

  • 每個線程都有一個名字,盡管上面的例子中沒有指定線程對象的name,但python會自動為線程指定一個名字。
  • 當線程的run方法完成時,該線程完成。
  • 程序無法控制線程的調度順序,但可以通過別的方式去影響線程調度的方式。

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()

結果如下,很明顯地看出線程之間是共享全局變量的。

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

當列表當作實參傳遞到線程中。

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()
  • 在一個進程內所有線程共享全局變量,很方便在多個進程間進行共享數(shù)據(jù)。
  • 優(yōu)劣并存的是線程對全局變量的隨意修改可能會造成多線程之間對全局變量的混亂。

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

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()

結果這樣:

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

每次運行不一樣,原因如下:

1、在my_num=0時,線程1取得my_num=0。此時系統(tǒng)把線程1調度為‘sleeping’狀態(tài),把線程2轉換為‘running’狀態(tài),t2也獲得my_num=0。
2、線程2對得到的值進行加1并賦給my_num,使得my_num=1。
3、系統(tǒng)又把線程2調度為‘sleeping’,把線程1轉為‘running’。線程線程1又把它之前得到的0加1后賦值給my_num。
4、這樣導致線程1和2都對my_num加1,但結果仍然是my_num=1。

重點:如果多個線程同時對同一個全局變量進行操作,會出現(xiàn)資源競爭問題,從而導致運算結果會不正確。

2.4 同步與互斥鎖

同步不是說一起同時,而是協(xié)同配合。比如上面的問題,線程1、2在操作my_num時,發(fā)現(xiàn)對方在用,就延遲一會再用,相互等待,兩個同時只執(zhí)行1個就可以做到同步。

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

當多個線程同時修改某一個共享數(shù)據(jù)時,就要進行同步控制。最簡單的機制就是互斥鎖。互斥鎖為資源的引入狀態(tài):鎖定/非鎖定。

某個線程需要修改一個共享數(shù)據(jù)時,先將這個數(shù)據(jù)進行“鎖定”,然后對其做修改。這個共享數(shù)據(jù)被鎖定時,其它線程不能修改,知道該數(shù)據(jù)的狀態(tài)變成“非鎖定”,其它線程才能再次鎖定該資源?;コ怄i保證每個只有一個線程進行寫入操作,從而保證了多線程情況下數(shù)據(jù)的準確性。

threading.Lock封裝了互斥鎖,可以方便的對其進行操作。方法如下:

mutex = threading.Lock()
mutex.acquire()
mutex.release()
  • 如果這個資源之前是沒上鎖的,那么acquire()不會堵塞。
  • 如果調用acquire()對這個資源進行上鎖之前,它已經被其它線程上了鎖,那么此時acquire()會堵塞,直到這個鎖被釋放為止。

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

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

上鎖解鎖的過程:當一個線程調用鎖的acquire()方法獲得鎖,鎖就進入“l(fā)ocked”的狀態(tài)。每次只有一個線程可以獲得鎖。如果另一個線程試圖獲得這個鎖,該線程會變?yōu)椤癰locked”,成為堵塞,直到擁有鎖的線程的release()方法釋放鎖后,鎖進入“unlock”狀態(tài)。線程調度程序從出于堵塞狀態(tài)中的線程選擇一個獲得鎖,并使得該線程進入到運行“running”狀態(tài)。

特點:

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

2.5 死鎖

在線程間共享多個資源的時候,如果兩個線程分別占有一部分資源并等待對方的資源,這樣就會造成死鎖。盡管死鎖很少發(fā)送,但一旦發(fā)生就會造成應用停止響應。

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()

運行結果如下:

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

這樣一來,就要考慮如何避免死鎖,通常兩種方案。

  • 程序設計盡量避免(銀行家算法、生產者與消費者)
  • 添加超時時間,釋放鎖

2.6 Condition與生產者與消費者問題

python提供的threading.Condition對象提供了對復雜線程同步支持的問題。

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

使用情景:線程首先acquire獲取條件變量,然后再進行一些條件判斷,如果條件滿足,則進行一些處理改變條件后再通過notify方法通知其它線程,其它出于wait狀態(tài)的線程接到條件后會重新進行條件判斷。若條件不滿足則wait,等待notify通知。如此不斷反復這一過程。

那經典的生產者與消費者的問題描述的是:假設一群生產者(Producer)和一群消費者(Consumer)通過一個市場來交換東西。生產者的策略是如果市場的剩余產品少于5個,那么就生產一個產品放到市場上。而消費者的策略是如果市場上剩余的產品多于5個,就消費1個產品。

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('生產者與消費者')
    condition = threading.Condition()
    a = Producer()
    a.start()
    b = Consumer()
    b.start()

2、進程

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

1、進程

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

進程:一個程序運行起來之后,運行的代碼和用到的資源稱之為進程,它是操作系統(tǒng)分配資源的基本單位。

2、進程的狀態(tài)

一般的計算機運行情況是任務數(shù)大于cpu數(shù),即一些任務正在運行,另一些任務在等待cpu執(zhí)行的情況,因此導致了任務有不同的狀態(tài)。

  • 就緒態(tài):運行的條件都已經滿足,正在等在cpu執(zhí)行。
  • 執(zhí)行態(tài):cpu正在執(zhí)行其功能。
  • 等待態(tài):等待某些條件滿足,例如一個程序sleep了,此時就處于等待態(tài)。

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

multiprocessing模塊是跨平臺版本的多進程模塊,提供了一個Process類來代表一個進程對象,這個對象可以理解為一個獨立的進程,可以執(zhí)行另外的事情。

1、簡單進程跑起來

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)

結果:

----running process
----new process
----running process
----new process
----running process
----new process
----running process
  • 創(chuàng)建子進程時,只需要傳入一個可執(zhí)行函數(shù)和對應參數(shù),創(chuàng)建一個Process()實例,用start()方法就可以了。

2、函數(shù)傳參及進程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)

結果:

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

可以看出,創(chuàng)建出來的進程是當前運行進程的子進程。

3、Process詳解

Process的語法結構:

Process([group [, target [, name [, args [, kwargs]]]]])
  • target:如果函數(shù)傳遞了函數(shù)的引用,可以認為創(chuàng)建的子進程就執(zhí)行它的代碼。
  • args:給target傳遞參數(shù),以元組的形式傳入。
  • kwargs:給target指定的函數(shù)傳遞命名參數(shù),字典形式。
  • name:給程序設定一個名字,也可以不設定。
  • group:指定進程組,大部分情況下用不到。

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

  • start():啟動子進程實例(創(chuàng)建子進程)。
  • is_alive():判斷子進程是否存活。
  • join():是否等待子進程執(zhí)行結束或等待多少秒。
  • terminate():不管任務是否完成,強制終止子進程。

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

name:當前進程的別名,默認為Process-N,N為從1開始遞增的整數(shù)。
pid:當前進程的pid。

4、進程之間不共享全局變量

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)

結果:

Process-1
process_one pid=8984 
[1, 2, 3, 4, 5, 6]
Process-2
process_two pid=9200 
[1, 2, 3, 4, 5]
  • 每個實例對象的name從1依次遞增。
  • 全局變量不共享。

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

進程是系統(tǒng)進行資源分配和調度的獨立單位。線程是一個實體,是cpu調度和分配的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中必不可少的資源(如程序計算器、一組寄存器和棧),但是它可以共享同屬一個進程的其它線程共享進程所擁有的全部資源。

同時總結有以下幾個特點。

  • 一個程序至少有一個進程,一個進程至少有一個線程,線程不能獨立執(zhí)行,必須依存在進程中。
  • 線程的劃分尺度小于進程(資源比進程少),使得多線程的并發(fā)性高。
  • 進程在執(zhí)行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。
  • 線程的執(zhí)行開銷小,不利于系統(tǒng)資源的管理和保護,進程相反。

2.4 進程之間的通信

Process之間有時需要通信,操作系統(tǒng)提供了很多機制來實現(xiàn)進程之間的通信。

1、Queen的使用

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

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('先判斷再寫入消息')
if not msg_queen.empty():
    for i in range(msg_queen.qsize()):
        # 先判斷再獲取
        print(msg_queen.get_nowait())

結果:

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語法

初始化Queen對象時,若括號沒有指定最大可接收消息數(shù)目或消息數(shù)目為負值,那就代表可接收的消息數(shù)沒有上限(除非內存使用完)。

常用方法介紹:

  • qsize():消息隊列的長度。
  • put(msg, block, timeout):試圖插入消息隊列,超時拋錯。
  • block默認為True,如果使用默認值,且沒有設置timeout,消息隊列已滿,那么程序將處于阻塞狀態(tài)(停在等待寫入狀態(tài)),直到從消息隊列騰出空間為止。如果設置了timeout,則會等待timeout秒,若還沒獲取到空間就拋錯。
  • put_nowait(msg):立即插入消息,已滿報錯。put_nowait(item)==put(item, False);
  • full():消息隊列是否已滿。
  • empty():消息隊列是否為空。
  • get(block, timeout):試圖獲取消息,超時拋錯。
    • block默認為True,如果使用默認值,且沒有設置timeout,消息隊列為空,那么程序將處于阻塞狀態(tài)(停在讀取狀態(tài)),直到從消息隊列中讀到消息為止。如果設置了timeout,則會等待timeout秒,若還沒獲取到就拋錯。
    • block為False,消息隊列為空,則會立即拋出異常。
  • get_nowait(msg):立即獲取消息,異常報錯。get_nowait()==get(False)

簡單跑跑。

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 進程池

當需要創(chuàng)建的子進程數(shù)量不多的時候,可以直接利用multiprocessing中的Process去動態(tài)創(chuàng)建多個進程,但是如果要創(chuàng)建成百上千個目標,手動去創(chuàng)建的進程的工作量較大,此時就可以用到multiprocessing模塊提供的Pool方法。

初始化進程池,可以制定一個最大進程數(shù),當有新的請求提交到Pool中時,如果池還沒滿,就會創(chuàng)建一個新的進程來執(zhí)行這個請求;如果進程池的數(shù)目已經達到最大值,那么該請求就會等待,指導進程池中的進程結束,才會用之前的進程來執(zhí)行新的任務。

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()

結果:

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

可以看出是重復利用進程的,且進程之間互不影響。

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

  • apply_async(func[, args[, kwargs]]):非阻塞方式調用func(并行執(zhí)行,阻塞方式必須等待上一個進程完了才能執(zhí)行下一個進程),args為參數(shù)列表,元組形式;kwargs是命名參數(shù)字典。
  • close():關閉進程池,不再接受其它任務。
  • terminate():不管任務是否完成,立即終止。
  • join():主進程阻塞,等待子進程結束退出,必須在close或terminate之后才能調用。

進程池中的Queue:

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

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

下面的實例演示了進程池中的進程如何通信:

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()

結果:

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 迭代器

迭代是一種訪問集合方式的一種方式。迭代器是可以記住其遍歷對象的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束,迭代器只能前進不能后退。

3.1.1 可迭代對象

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

那么問題來了,為什么有的數(shù)據(jù)類型可以用for…in…進行迭代,而有的數(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

整型對象不是可迭代的。

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

我們自定義一個容器類型,里面存放了3個數(shù)據(jù),嘗試去遍歷它,發(fā)現(xiàn)報錯。即這個自定義對象也是不可以遍歷的。那么如何判斷一個對象是否是可以遍歷的?

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

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

可以使用instance()判斷一個對象是否是Iterable對象。

以上面的mylist為例。

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

結果:

False
True
True
False
True

3.1.3 可迭代對象的本質

我們從表面去看,對可迭代對象來說,每迭代一次都會返回下一個元素,直到這個對象遍歷完畢。那么在這個過程中就應該有一個“變量”在記錄每次迭代后當前的位置,以便下次都可以獲取到下一條數(shù)據(jù)。我們稱這個變量為迭代器(iterator)。

可迭代對象的本質就是可以向我們提供一個這樣的“變量”,即迭代器幫助我們去進行迭代遍歷使用。

可迭代對象通過iter()方法向我們提供一個迭代器,我們在迭代一個可迭代對象時,實際上就是獲取該對象提供的迭代器,然后通過這個迭代器依次獲取該對象的每個數(shù)據(jù)。

那么就可以這么說,一個具備iter()方法的對象就是可迭代對象。

還是上面mylist那個自定義對象,添加如下代碼:

def __iter__(self):
    pass

運行:

print(isinstance(mylist, Iterable))

結果:

True

那么按道理來說,mylist就可以遍歷了才是,如下:

for i in mylist:
    print(i)

結果:

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等都是可迭代對象,我們可以通過iter()函數(shù)獲取這些可迭代對象的迭代器,然后我們可以通過獲取迭代器的next()函數(shù)來獲取下一個數(shù)據(jù)。iter()函數(shù)實際上就是調用了可迭代對象的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))

結果:

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

當我們迭代完最后一個數(shù)據(jù)之后,再次調用next()函數(shù)就會拋出StopIteration的異常,說明我們所有的數(shù)據(jù)都已經迭代完成,不需要再執(zhí)行next()函數(shù)了。

3.1.5 如何判斷一個對象是否是迭代器

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

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

結果:

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

從上面的一步步走過來,我們清楚迭代器是幫我們記錄每次迭代到訪問的位置,當我們對迭代器使用next()函數(shù)的時候,迭代器會向我們返回記錄它的下一個數(shù)據(jù)的位置。實際上,使用next()函數(shù)的時候,其調用的就是迭代器對象的next方法。但這還不夠,python要求迭代器本身也是可迭代的,所以還要為迭代器實現(xiàn)iter方法,而iter要返回一個迭代器,迭代器自身就是一個迭代器,所以返回其自身就好。

簡而言之,一個實現(xiàn)了iternext方法的對象就是迭代器。

然后上面的自定義可以寫成這樣。

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)的本質

for item in iterable 循環(huán)的本質就是先通過iter()獲取可迭代對象Iterable的迭代器,然后不斷調用next()方法來獲取下一個值并將其復制給item,當遇到StopIteration的異常后循環(huán)結束。

3.1.8 迭代器的應用場景

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

下面利用迭代器實現(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))

結果為:

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方法)時按照特定的規(guī)律進行生成。但我們在實現(xiàn)一個迭代器時,關于當前迭代的狀態(tài)需要我們自己去記錄,進而才能依據(jù)當前狀態(tài)去生成下一個數(shù)據(jù)。為了達到記錄當前狀態(tài),并配合next()函數(shù)進行迭代使用,我們可以采用更簡潔的用法,即生成器generator()。

3.2.1 創(chuàng)建生成器

1、利用生成器符號

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

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

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

結果:

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

創(chuàng)建L和G的區(qū)別僅在于最外層的()和[],L是一個列表,G是一個生成器。我們可以直接打印L的每個元素,對于生成器G,我們可以按照迭代器的使用方法來使用,即可以通過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)

結果為:

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

2、yield

generator非常強大。如果推算的算法比較復雜,用類似列表生成式的for循環(huán)無法實現(xiàn)的時候,還可以用函數(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)

結果:

0
1
1
2
3
5

在使用生成器實現(xiàn)的方式中,我們將原本在迭代器中next方法中實現(xiàn)的基本邏輯放到一個函數(shù)中實現(xiàn),現(xiàn)在將每次迭代器返回數(shù)值的return換成了yield,此時定義的函數(shù)就不再是函數(shù),而是一個生成器。簡單來說:只要有yield關鍵字的就稱為生成器。

但是用for循環(huán)調用generator時,發(fā)現(xiàn)拿不到generator的return語句的返回值。如果想獲取返回值,必須捕獲StopIteration錯誤,返回值包含在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

結果:

0
1
1
2
3
done

總結:

  • 試用了yield關鍵字的函數(shù)就不再是函數(shù),而是生成器。
  • yield作用有兩點:保存當前的運行狀態(tài)(斷點),然后暫停執(zhí)行,即生成器被掛起;將yield關鍵字后面表達式的值作為返回值返回,此時可以理解為起到了return作用。
  • 使用next()函數(shù)可以生成器從斷點處繼續(xù)執(zhí)行,即喚醒生成器(函數(shù))。
  • py3中的生成器可以使用return返回最終運行的返回值,而py2中的生成器不允許使用個return返回一個返回值(可以使用return語句從生成器中退出,但是不能return后面不能有任何表達式)。

3.2.2 使用send喚醒

除了上面利用next()函數(shù)來喚醒生成器繼續(xù)執(zhí)行外,還可以使用send()函數(shù)來喚醒其繼續(xù)執(zhí)行。使用send()函數(shù)的另一個好處是可以同時向斷點處附加一個數(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))

結果:

0
None
1
daocoder
2
None
3

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

3.3 協(xié)程

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

3.3.1 協(xié)程簡介

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

通俗的理解就是:在一個線程的某個函數(shù),可以在任何地方保存當前函數(shù)的一些臨時變量等信息,談后切換到另一個函數(shù)中執(zhí)行,注意這里不是通過調用函數(shù)的方式去做到的,并且切換的次數(shù)以及什么時候切換到原來的函數(shù)都由開發(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)

結果為:

work1
work2
work1
work2
work1
work2

3.3.3 greenlet

為了更好的使用協(xié)程來完成任務,py提供了greenlet對其封裝,從而使切換任務變得更加簡單。

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()

結果為:

work1
work2
work1
work2
work1
work2

3.3.3 gevent

greenlet已經實現(xiàn)了協(xié)程,但是如上面代碼所示,它還需要人工進行切換,由此py提供了一個更強大的封裝gevent,它能夠自動切換任務。

其原理是當一個greenlet遇到一個IO(網絡、文件等輸入輸出操作)時,就自動切換到其它的greenlet,等到IO操作完成,再在適當?shù)臈l件下切回來繼續(xù)執(zhí)行。

由于IO操作非常耗時,經常使程序處于等待狀態(tài),有了gevent為我們自動切換協(xié)程,就保證總有greenlet在運行,而不是等待IO。

1、無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()

結果是:

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個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()

結果:

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遇到其耗時操作時,自動切換協(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()

結果:

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模塊的耗時并沒有使其自動切換上下文。

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()

結果:

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)>

另一種寫法:

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')
])

結果為:

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')>
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容