python 基础(十)
一、協程
協程,又稱微線程。協程是一種用戶態的輕量級線程。
協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。因此:協程能保留上一次調用時的狀態(即所有局部狀態的一個特定組合),每個過程重入時,就相當于進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。
協程的好處:
無需線程上下文切換的開銷
無需原子操作鎖定及同步的開銷
方便切換控制流,簡化編程模型
高并發+高擴展+低成本:一個CPU支持上萬的協程都不是問題。所以很適合用于高并發處理。
協程的缺點:
無法利用多核資源:協程的本質是個單線程,它不能同時將單個CPU的多個核用上,協程需要和進程配合才能運行在多CPU上,當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序。
使用yield實現協程操作例子:
#!/usr/bin/python #-*- conding:utf-8 -*- import time,queue def consumer(name):print("-->starting eating baozi...")while True:new_baozi = yieldprint("[%s] 在吃包子 %s " % (name,new_baozi)) def producer():r = person.__next__()r = person.__next__()n = 0while n < 5:n +=1person.send(n)person2.send(n)print("\033[32;1m[老板]\033[0m 在生產包子%s" %n) if __name__ == '__main__':person = consumer("人1")person2 = consumer("人2")p = producer() View CodeGreenlet
#!/usr/bin/env python # --*- coding:utf8 -*- from greenlet import greenlet def test1():print(12)gr2.switch()print(34)gr2.switch() def test2():print(56)gr1.switch()print(78)gr1.switch()gr1 = greenlet(test1) gr2 = greenlet(test2) gr1.switch() View Code結果輸出:
12
56
34
78
Gevent
Gevent是一個第三方庫,可以輕松通過gevent實現并發同步或異步編程,在gevet中用到的主要模式是Greenlet,它是以C擴展模塊形式接入Python的輕量級協程。Greenlet全部運行在主程序操作系統進程的內部,但它們被協作式的調整。
#!/usr/bin/env python # --*- coding:utf8 -*- import gevent def foo():print('Running in foo')gevent.sleep(0)print('Explicit context switch to foo again')def bar():print('Explicit context to bar')gevent.sleep(0)print('Implicit context switch back to bar') gevent.joinall([gevent.spawn(foo),gevent.spawn(bar), ]) View Code同步與異步的性能區別
#!/usr/bin/env python # --*- coding:utf8 -*- import gevent def task(pid):"""Some non-deterministic task"""gevent.sleep(0.5)print('Task %s done' % pid) def syschronous():for i in range(1,10):task(i) def asynchronous():threads = [gevent.spawn(task,i) for i in range(10)]gevent.joinall(threads) print('Synchronous:') syschronous() print('Asynchronous:') asynchronous() View Code上面的程序的重要部分是將task函數封裝到Greenlet內部線程的gevent.spawn。初始化的greenlet列表放在數組threads中,此數組被傳給gevent.joinall函數,后者阻塞當前流程,并執行所有給定的greenlet。執行流程只會在所有greenlet執行完后才會繼續向下走。
遇到IO阻塞時會切換任務之[爬蟲版]
#!/usr/bin/env python # --*- coding:utf8 -*- from urllib import request import gevent,time from gevent import monkey monkey.patch_all() #把當前程序中的所有io操作都做上標記 def spider(url):print("GET:%s" % url)resp = request.urlopen(url)data = resp.read()print("%s bytes received from %s ..." % (len(data),url)) urls = ["https://www.python.org/","https://www.yahoo.com/","https://github.com/" ] start_time = time.time() for url in urls:spider(url) print("同步耗時:",time.time() - start_time)async_time_start = time.time() gevent.joinall([gevent.spawn(spider,"https://www.python.org/"),gevent.spawn(spider,"https://www.yahoo.com/"),gevent.spawn(spider,"https://github.com/"), ]) start_time = time.time() for url in urls:spider(url) print("同步耗時:",time.time() - start_time)async_time_start = time.time() gevent.joinall([gevent.spawn(spider,"https://www.python.org/"),gevent.spawn(spider,"https://www.yahoo.com/"),gevent.spawn(spider,"https://github.com/"), ]) print("異步耗時",time.time() - async_time_start) View Code?二、sellect、poll、epoll三者的區別
select
select最早于1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個文件描述符的數組,當select()返回后,該數組中就緒的文件描述符便會被內核修改標志位,使得進程可以獲得這些文件描述符從而進行后續的讀寫操作。
select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。
select的一個缺點在于單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,不過可以通過修改宏定義甚至重新編譯內核的方式提升這一限制。
另外,select()所維護的存儲大量文件描述符的數據結構,隨著文件描述符數量的增大,其復制的開銷也線性增長。同時,由于網絡響應時間的延遲使得大量TCP連接處于非活躍狀態,但調用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。
poll
poll在1986年誕生于System V Release 3,它和select在本質上沒有多大差別,但是poll沒有最大文件描述符數量的限制。
poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制于用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數量的增加而線性增大。
另外,select()和poll()將就緒的文件描述符告訴進程后,如果進程沒有對其進行IO操作,那么下次調用select()和poll()的時候將再次報告這些文件描述符,所以它們一般不會丟失就緒的消息,這種方式稱為水平觸發(Level Triggered)。
epoll
直到Linux2.6才出現了由內核直接支持的實現方法,那就是epoll,它幾乎具備了之前所說的一切優點,被公認為Linux2.6下性能最好的多路I/O就緒通知方法。
epoll可以同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有采取行動,那么它將不會再次告知,這種方式稱為邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當復雜。
epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這里也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統調用時復制的開銷。
另一個本質的改進在于epoll采用基于事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法后,內核才對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。
轉載于:https://www.cnblogs.com/chaishao/p/5957204.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的python 基础(十)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谭浩强C语言程序设计 学习辅导练习题
- 下一篇: 在Xcode8中 如何添加.pch文件