1. 程式人生 > >多工--程序和協程

多工--程序和協程

什麼是程序?

程式在沒執行起來之前是死的,程式執行起來後就是程序,程序跟程式之間的區別就是程序擁有資源,例如登陸QQ之後,QQ可以呼叫音效卡、攝像頭等。

 

兩個程序之間的通訊,利用佇列Queue,放到記憶體裡,一個寫,一個讀。缺點就是佇列只能在同一個程式或者電腦中執行,要在多臺電腦之間進行,用到快取redis。

import multiprocessing


def download_data(q):
    # 下載資料
    data = [11, 22, 33, 44]
    for i in data:
        q.put(i)


def
analysis_data(q): while True: data = q.get() print(data) if q.empty(): break def main(): q = multiprocessing.Queue(4) p1 = multiprocessing.Process(target=download_data, args=(q, )) p2 = multiprocessing.Process(target=analysis_data, args=(q, )) p1.start() p2.start()
if __name__ == "__main__": main()

 程序池

程序池裡面有預先指定好的程序數,當很多工一起過來時,如果程序被呼叫完,其他任務就一直等著,等待完成任務的程序來呼叫它們。好處就是減少了程序的建立和銷燬所花的時間和資源。

 


 

協程

1、迭代器

在原來的基礎上去得到一個新的東西,這就是迭代。

 

# 判斷是否可迭代
>>> from collections import Iterable
>>> isinstance([11, 22, 33], Iterable)
True

 

from collections import Iterable


class Classmatename():
    def __init__(self):
        self.names = list()

    def add(self, name):
        self.names.append(name)

    def __iter__(self):
        """如果想要一個物件稱為一個 可以迭代的物件,既可以使用for,那麼必須實現__iter__方法"""
        pass

classmatename = Classmatename()

classmatename.add("張三")
classmatename.add("李四")
classmatename.add("王五")

print(isinstance(classmatename, Iterable)) # ------》 True

 但如何迴圈列印列表裡面的值呢?

首先先想一下當我們迴圈列印列表的時候發生了什麼,每次迴圈,就會列印一個值,下一次迴圈列印下一個值,由此我們應該可以猜想有一個東西,在記錄著我們列印到哪了,這樣才可以在下一次列印接下來的值。對於我們上面建立的類,現在它已經是可以迭代了,但是還沒有一個東西來記錄它應該列印什麼,列印到了哪裡。

for item in xxx_obj:
    pass

1、判斷xxx_obj是否是可以迭代;
2、在第一步成立的前提下,呼叫iter函式,得到xxx_obj物件的__iter__方法的返回值;
3、__iter__方法的返回值是一個  迭代器

 什麼是迭代器?一個物件裡面有“__iter__”方法,我們稱之為  可以迭代。如果"__iter__"方法返回的物件裡面既有“__iter__”又有"__next__"方法,那麼我們稱這個物件為  迭代器

所以為了可以列印,這裡需要兩個條件:1、有iter值;2、iter值返回一個物件引用。這個物件裡面除了iter方法外還有一個next方法。

因此大體過程就是for迴圈呼叫,首先判斷這個物件是不是可迭代的(有"__iter__",是),接下來自動用iter方法呼叫"__iter__",獲取返回的“物件引用”。接下來for迴圈呼叫這個物件裡面的“__next__”方法,調一次,next方法返回什麼,就輸出什麼給item列印。

 

from collections import Iterator

classmate_Iterator = iter(classmatename) # 用iter獲取迭代器
# 判斷是否是迭代器
print(isinstance(classmate_Iterator, Iterator))

 

import time


class Classmatename():
    def __init__(self):
        self.names = list()
        self.current_num = 0

    def add(self, name):
        self.names.append(name)

    def __iter__(self):
        """如果想要一個物件稱為一個 可以迭代的物件,既可以使用for,那麼必須實現__iter__方法"""
        # 建立例項物件
       return ClassIterator(self) # self指向這個類本身,然後傳給類ClassIterator

class ClassIterator(): def __init__(self, obj): self.obj = obj # self.obj指向例項物件Classmatename self.current_num = 0 def __iter__(self): pass def __next__(self): if self.current_num < len(self.obj.names): ret = self.obj.names[self.current_num] self.current_num += 1 # 注意這裡的self.current_num=0要放在__init__下,如果current_num=0放在__next__下,則for迴圈每次呼叫__next__時,current_num都會被重新記零 return ret else: raise StopIteration # 丟擲異常,告訴for迴圈可以結束了

classmatename = Classmatename() classmatename.add("張三") classmatename.add("李四") classmatename.add("王五") for item in classmatename: print(item) time.sleep(1)

 另一種辦法,寫在一起

import time


class Classmatename():
    def __init__(self):
        self.names = list()
        self.current_num = 0

    def add(self, name):
        self.names.append(name)

    def __iter__(self):
        """如果想要一個物件稱為一個 可以迭代的物件,既可以使用for,那麼必須實現__iter__方法"""
        # 建立例項物件
        return self # 返回自身給for迴圈呼叫裡面的__next__

    def __next__(self):
        if self.current_num < len(self.names):
            ret = self.names[self.current_num]
            self.current_num += 1
            return ret
        else:
            raise StopIteration # 丟擲異常,告訴for迴圈可以結束了



classmatename = Classmatename()
classmatename.add("張三")
classmatename.add("李四")
classmatename.add("王五")


for item in classmatename:
    print(item)
    time.sleep(1)

 迭代器的優點

優先我們先來了解下Python2中的range和xrange區別

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> xrange(10)
xrange(10)

由上圖可知,range和xrange的區別就是range會直接返回生成的結果,而xrange返回的是生成這個結果的方式,什麼時候需要呼叫裡面的值,它才會去生成,所以xrange佔用的記憶體空間要小很多。(這個問題在Python3已經解決了,Python3的range相當於Python2裡面的xrange)

迭代器也是一樣,生成的是呼叫值的方式,什麼時候要呼叫值,才去生成,因此佔用很少的記憶體空間。

# 用迭代器的方法生成斐波那契數列
class Fibonacci(object):
    def __init__(self, all_num):
        self.all_num = all_num
        self.current_num = 0
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_num < self.all_num:
            ret = self.a
            self.a, self.b = self.b, self.a+self.b
            self.current_num += 1
            return ret
        else:
            raise StopIteration

fibo = Fibonacci(10)
for num in fibo:
    print(num)

a = (11, 22, 33)
list(a)      # 首先生成一個空列表,然後迴圈呼叫a裡面的迭代器,把值一個個放到列表中去
[11, 22, 33] 

 


 

生成器(是一種特殊的迭代器)

生成生成器的第一種方式

>>> nums1 = [x*2 for x in range(10)]
>>> nums1
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

 

>>> nums2 = (x*2 for x in range(10))
>>> nums2
<generator object <genexpr> at 0x00AF5180>

其中,num2就是生成器。可以通過for迴圈遍歷裡面的值。num1和num2的區別就是前面說的,num2不佔用空間,值在需要時才會被生成。

生成生成器的第二種方式

# 用普通函式生成斐波那契數列
def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        print(a)
        a, b = b, a+b
        current_num += 1

         
create_num(10)

用生成器的方法生成斐波那契數列,新增yield

# 生成器
def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        yield a  # 如果一個函式中有yield語句,那麼這個就不再是函式,而是一個生成器的模板
a, b
= b, a+b current_num += 1 # 如果在呼叫create_num的時候,發現這個函式有yield,那麼不在是呼叫函式,而是建立一個生成器物件 obj = create_num(10)
for num in obj: # for迴圈呼叫過程。首先建立obj,當開始執行for迴圈時,程式開始向下走,
  print(num)  # 當到達yield a的時候停止,把a的值傳給num,打印出來。然後for繼續呼叫,於是從停止的位置也就是yield那裡繼續向下執行,而不會從函式的開頭重新運行了。

 用 "ret = next(obj)" 可以一次呼叫一個值用來驗證下。

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        yield a
        a, b = b, a+b
        current_num += 1
    return ".....ok....."

obj = create_num(10)
while True:
    try:
        ret = next(obj)
        print(ret)
    except Exception as ret:
        print(ret.value) # 這個value就是return返回的值
        break

 第二中呼叫生成器的方法:send() (一般不用做第一次啟動,如果非要,send()裡面只能傳遞None)

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        ret = yield a
        print(">>>>>>>>ret>>>>>>>", ret)
        a, b = b, a+b
        current_num += 1


obj = create_num(10)
ret = next(obj)
print(ret)
ret = obj.send("hahaha") # 首先程式執行到"yield a"時停止,把“a=0”傳給ret打印出來。接下來執行"send("hahaha")”,從“ret=yield a“開始,此時"yield a"並沒有返回值給等號左邊的ret,那麼就把"hahaha"
print(ret)               # 傳給ret,由"print(">>>>>>>>>ret>>>>>>>>>", ret)"打印出來。再繼續執行接下來的步驟。
# 結果返回
0
>>ret>>>>>>> hahaha
1

 總結:迭代器特點是佔用記憶體空間小,什麼時候用,什麼時候生成;

  生成器有迭代器的特點,而且它最大的特點就是可以執行到一半時暫停,返回結果,然後再繼續在原來基礎上繼續執行。

 用yield實現多工

import time


def task_1():
    while True:
        print("--------1---------")
        time.sleep(0.1)
        yield


def task_2():
    while True:
        print("--------2---------")
        time.sleep(0.1)
        yield


def main():
    t1 = task_1()
    t2 = task_2()
    while True:
        next(t1)
        next(t2)


if __name__ == "__main__":
    main()

 

協程(單程序單執行緒)最大的意義,把原本等待的時間利用起來去做別的事情。

協程依賴於執行緒,執行緒依賴於程序。

有時程式寫了很多行了,用的是time.sleep(),這時不想用gevent裡面的gevent.sleep()方法,可以給程式打補丁

 

小案例,用gevent實現圖片下載

import urllib.request
import gevent
from gevent import monkey


monkey.patch_all()


def download_img(file_name, url):
    img = urllib.request.urlopen(url)
    img_content = img.read()
    with open(file_name, "wb") as f:   # 這裡沒有用time模組是因為網路下載過程中本來就會延時,相當於我們前面例項的time.sleep()
        f.write(img_content)


def main():
    gevent.joinall([
        gevent.spawn(download_img, "1.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2018/08/31/3279944_20180831104533_small.jpg"),
        gevent.spawn(download_img, "2.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2018/11/14/910907_20181114154402_small.jpg")
    ])


if __name__ == "__main__":
    main()