協(xié)程的定義 協(xié)程,又稱微線程,纖程。英文名Coroutine。 首先我們得知道協(xié)程是啥?協(xié)程其實..."/>
您好,登錄后才能下訂單哦!
本文研究的主要是python中協(xié)程的相關(guān)問題,具體介紹如下。
Num01–>協(xié)程的定義
協(xié)程,又稱微線程,纖程。英文名Coroutine。
首先我們得知道協(xié)程是啥?協(xié)程其實可以認為是比線程更小的執(zhí)行單元。 為啥說他是一個執(zhí)行單元,因為他自帶CPU上下文。這樣只要在合適的時機, 我們可以把一個協(xié)程 切換到另一個協(xié)程。 只要這個過程中保存或恢復(fù) CPU上下文那么程序還是可以運行的。
那么這個過程看起來和線程差不多。其實不然, 線程切換從系統(tǒng)層面遠不止保存和恢復(fù) CPU上下文這么簡單。 操作系統(tǒng)為了程序運行的高效性每個線程都有自己緩存Cache等等數(shù)據(jù),操作系統(tǒng)還會幫你做這些數(shù)據(jù)的恢復(fù)操作。 所以線程的切換非常耗性能。但是協(xié)程的切換只是單純的操作CPU的上下文,所以一秒鐘切換個上百萬次系統(tǒng)都抗的住。
協(xié)程有一個問題,就是系統(tǒng)并不感知,所以操作系統(tǒng)不會幫你做切換。 那么誰來幫你做切換?讓需要執(zhí)行的協(xié)程更多的獲得CPU時間才是問題的關(guān)鍵。
舉個例子如下:
目前的協(xié)程框架一般都是設(shè)計成 1:N 模式。所謂 1:N 就是一個線程作為一個容器里面放置多個協(xié)程。 那么誰來適時的切換這些協(xié)程?答案是有協(xié)程自己主動讓出CPU,也就是每個協(xié)程池里面有一個調(diào)度器, 這個調(diào)度器是被動調(diào)度的。意思就是他不會主動調(diào)度。而且當(dāng)一個協(xié)程發(fā)現(xiàn)自己執(zhí)行不下去了(比如異步等待網(wǎng)絡(luò)的數(shù)據(jù)回來,但是當(dāng)前還沒有數(shù)據(jù)到), 這個時候就可以由這個協(xié)程通知調(diào)度器,這個時候執(zhí)行到調(diào)度器的代碼,調(diào)度器根據(jù)事先設(shè)計好的調(diào)度算法找到當(dāng)前最需要CPU的協(xié)程。 切換這個協(xié)程的CPU上下文把CPU的運行權(quán)交個這個協(xié)程,直到這個協(xié)程出現(xiàn)執(zhí)行不下去需要等等的情況,或者它調(diào)用主動讓出CPU的API之類,觸發(fā)下一次調(diào)度。
在IO密集型的程序中由于IO操作遠遠慢于CPU的操作,所以往往需要CPU去等IO操作。 同步IO下系統(tǒng)需要切換線程,讓操作系統(tǒng)可以在IO過程中執(zhí)行其他的東西。 這樣雖然代碼是符合人類的思維習(xí)慣但是由于大量的線程切換帶來了大量的性能的浪費,尤其是IO密集型的程序。
所以人們發(fā)明了異步IO。就是當(dāng)數(shù)據(jù)到達的時候觸發(fā)我的回調(diào)。來減少線程切換帶來性能損失。 但是這樣的壞處也是很大的,主要的壞處就是操作被 “分片” 了,代碼寫的不是 “一氣呵成” 這種。 而是每次來段數(shù)據(jù)就要判斷 數(shù)據(jù)夠不夠處理哇,夠處理就處理吧,不夠處理就在等等吧。這樣代碼的可讀性很低,其實也不符合人類的習(xí)慣。
但是協(xié)程可以很好解決這個問題。比如 把一個IO操作 寫成一個協(xié)程。當(dāng)觸發(fā)IO操作的時候就自動讓出CPU給其他協(xié)程。要知道協(xié)程的切換很輕的。 協(xié)程通過這種對異步IO的封裝 既保留了性能也保證了代碼的容易編寫和可讀性。在高IO密集型的程序下很好。但是高CPU密集型的程序下沒啥好處。
import time def A(): while True: print("----我是A函數(shù)---") yield time.sleep(0.5) def B(c): while True: print("----我是B函數(shù)---") next(c) time.sleep(0.5) if __name__ == '__main__': a = A() B(a) # 結(jié)果如下: # ----我是B函數(shù)--- # ----我是A函數(shù)--- # ----我是B函數(shù)--- # ----我是A函數(shù)--- # ----我是B函數(shù)--- # ----我是A函數(shù)--- # ----我是B函數(shù)--- # ----我是A函數(shù)--- # ----我是B函數(shù)--- # ----我是A函數(shù)--- # ......
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Author : xiaoke from greenlet import greenlet import time def test1(): while True: print("---我是A函數(shù)--") gr2.switch() time.sleep(0.5) def test2(): while True: print("---我是B函數(shù)--") gr1.switch() time.sleep(0.5) def main(): # 切換到gr1中運行 gr1.switch() if __name__ == '__main__': gr1 = greenlet(test1) gr2 = greenlet(test2) main() # 結(jié)果如下: # ---我是A函數(shù)-- # ---我是B函數(shù)-- # ---我是A函數(shù)-- # ---我是B函數(shù)-- # ---我是A函數(shù)-- # ---我是B函數(shù)-- # ---我是A函數(shù)-- # ---我是B函數(shù)-- # ......
原理:其原理是當(dāng)一個greenlet遇到IO(指的是input output 輸入輸出,比如網(wǎng)絡(luò)、文件操作等)操作時,比如訪問網(wǎng)絡(luò),就自動切換到其他的greenlet,等到IO操作完成,再在適當(dāng)?shù)臅r候切換回來繼續(xù)執(zhí)行。
由于IO操作非常耗時,經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動切換協(xié)程,就保證總有g(shù)reenlet在運行,而不是等待IO
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Author : xiaoke import gevent def task1(n): for i in range(n): print('----task1-----') gevent.sleep(1) # time.sleep(1) # time.sleep沒有讓gevent感知到等待 def task2(n): for i in range(n): print('----task2-----') gevent.sleep(1) # time.sleep(1) def main(): g1 = gevent.spawn(task1, 5) g2 = gevent.spawn(task2, 5) g1.join() g2.join() if __name__ == "__main__": main() # 結(jié)果如下: # ----task1----- # ----task2----- # ----task1----- # ----task2----- # ----task1----- # ----task2----- # ----task1----- # ----task2----- # ----task1----- # ----task2-----
實際代碼里,我們不會用gevent.sleep()去切換協(xié)程,而是在執(zhí)行到IO操作時,gevent自動切換,代碼如下
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Author : xiaoke import urllib.request # py3 import gevent from gevent import monkey # 猴子補丁,將標(biāo)準(zhǔn)庫的涉及IO操作方法替換成gevent monkey.patch_all() # 協(xié)程的任務(wù)函數(shù) def my_download(url): print('GET %s' % url) response = urllib.request.urlopen(url) data = response.read() print('下載 %d bytes from %s' % (len(data), url)) def main(): g1 = gevent.spawn(my_download, 'http://www.google.cn') g2 = gevent.spawn(my_download, 'http://www.qq.com') g3 = gevent.spawn(my_download, 'http://www.baidu.com') gevent.joinall([g1, g2, g3]) # 等待指定的協(xié)程結(jié)束 if __name__ == "__main__": main() # 結(jié)果如下: # GET http://www.google.cn # GET http://www.qq.com # GET http://www.baidu.com # 下載 102221 bytes from http://www.baidu.com # 下載 52297 bytes from http://www.qq.com # 下載 3213 bytes from http://www.google.cn #從上能夠看到是先獲取baidu的相關(guān)信息,然后依次是qq #google,但是收到數(shù)據(jù)的先后順序不一定與發(fā)送順序相同, #這也就體現(xiàn)出了異步,即不確定什么時候會收到數(shù)據(jù),順序不一定.
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Author : xiaoke import socket import gevent from gevent import monkey #猴子補丁,將標(biāo)準(zhǔn)庫的涉及IO操作方法替換成gevent monkey.patch_all() # 需要為客戶端提供服務(wù) def do_service(connect_socket): while True: # tcp recv() 只會返回接收到的數(shù)據(jù) recv_data = connect_socket.recv(1024) # if recv_data == b'': if len(recv_data) == 0: # 發(fā)送方關(guān)閉tcp的連接,recv()不會阻塞,而是直接返回'' # print('client %s close' % str(client_addr)) # s.getpeername() s.getsockname() print('client %s close' % str(connect_socket.getpeername())) break print('recv: %s' % recv_data.decode('gbk')) def main(): listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用 listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) my_addr = ('192.168.105.125', 8080) listen_socket.bind(my_addr) listen_socket.listen(5) # 設(shè)置套接字成監(jiān)聽,5表示一個己連接隊列長度 print('listening...') while True: # 接受連接請求,創(chuàng)建連接套接字,用于客戶端連通信 connect_socket, client_addr = listen_socket.accept() # accept默認會引起阻塞 # 新創(chuàng)建連接用的socket, 客戶端的地址 # print(connect_socket) print(client_addr) # 每當(dāng)來新的客戶端連接,創(chuàng)建協(xié)程,由協(xié)程和客戶端通信 coroutine_do_service = gevent.spawn(do_service, connect_socket) if __name__ == "__main__": main()
關(guān)于協(xié)程的問題,面試中好像也會時常被問到,大家一定要注意理解,概念,怎么去實現(xiàn)。
以上就是本文關(guān)于Python中協(xié)程用法代碼詳解的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。