① 簡述python進程,線程和協程的區別及應用場景
協程多與線程進行比較
1) 一個線程可以多個協程,一個進程也可以單獨擁有多個協程,這樣python中則能使用多核CPU。
2) 線程進程都是同步機制,而協程則是非同步
3) 協程能保留上一次調用時的狀態,每次過程重入時,就相當於進入上一次調用的狀態
② 如何用python寫一個協程
作者:LittleCoder
鏈接:https://www.hu.com/question/54483694/answer/139785021
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請註明出處。
yield`和`yield from`的區別
`yield`題主肯定不陌生,而`yield from`是PEP 380中新增的一個特性。
PEP 380的名字是嵌套子迭代器的語法糖(我喜歡這么翻譯,原文是:Syntax for Delegating to a Subgenerator)。
既然是語法糖,那麼肯定本來是有別的寫法的,這里給出本來的寫法:
def subgen():
for i in range(3):
yield 'subgen: %s' % i
return 'subgen returned'def gen():
r = yield from subgen()
print('r = "%s"' % r)
yield rdef gen_without_yield_from():
sg = subgen()
try:
while 1:
yield sg.send(None)
except StopIteration as e:
yield e.valueprint('* [gen] get all values')for v in gen_without_yield_from():
print('get value: %s' % v)print('* [gen_without_yield_from] get all values')for v in gen_without_yield_from():
print('get value: %s' % v)
不難看出,`yield`子迭代器是把子迭代器直接傳遞出去,`yield from`子迭代器是把子迭代器的值一個一個傳出去。
雖然實際把子迭代器當做一個對象直接傳遞出去也沒有問題,也有使用場景(生成迭代器的迭代器)。
但在協程中相較於這個令人愉快的語法糖而言,直接傳遞就顯得沒有必要且礙事了。
畢竟我希望使用一個子迭代器是把子迭代器中的代碼都運行一遍而不是直接就把這個子迭代器傳出來讓我自己操作。
所以如果你把子迭代器直接傳了出去,asyncio就判斷你在做一件奇怪的事情並報了錯。
那麼,回到問題,給出的程序要怎麼通過`yield`調用呢?
# 源程序@asyncio.coroutinedef hello():
print("Hello world!")
yield from asyncio.sleep(1)
print("Hello again!")# 使用[email protected] hello():
print("Hello world!")
for v in asyncio.sleep(1):
yield v
print("Hello again!")
協程和迭代器的區別
舉個比喻,迭代器和協程就像火葯和槍械,利用火葯的特性輔助各種其他東西才造出了槍械。
迭代器就最簡單的本質而言就是一個可以暫停的程序。
那麼就有這樣一個合理的聯想,我是不是可以節省下所有不必要的例如等待網站響應的等待時間。
就是我把我的請求發過去以後就把這個程序暫停下來,開啟別的程序,等到響應來了再叫我回到這個程序。
那麼等待網站響應的時間也就完全沒有浪費了,比原來傻傻的等著網站響應真是優秀了許多。
這就是協程。
所以,為什麼看上去都是`generator`,迭代器不會天生成為協程呢?
因為沒有一個知道什麼時候應該叫你回到這個程序的人。
這個人就是`event_loop`(消息循環)。
回到問題,協程是否可以脫離`event_loop`(消息循環)調用。
講道理是不可以的,但合理聯想一下是不是一直不停的告訴程序又到你了就行了。
像這樣:
@asyncio.coroutinedef gen():
for i in range(3):
yield ifor i in gen():
print(i)print('end')
的確有些協程這樣是可以運行的(這些協程為什麼要寫成協程?)。
但終究你是在不應該告訴程序到你的時候告訴了他這件事情。
所以顯然獲取數據的話當時數據根本沒有傳到,`sleep`的話就根本沒有了`sleep`的效果。
只是看上去能夠運行,實際完全沒有用。
asyncio還為此特地加了一個斷言,如果你這樣調用`asyncio.sleep`,asyncio會發現你在偽裝消息循環騙他。
協程的原理
這是另一個看上去能夠運行,實際上完全沒有用的事情。
這雖然不是你想問的問題,但你已經碰到了也遲早會意識到,所以一並講了。
這個問題應該是這樣的:為什麼我寫出來的協程完全沒有協程的效果?
import time, [email protected] sleep(symbol, i):
time.sleep(i)
print('[%s] finished')loop = asyncio.get_event_loop()tasks = [sleep('A', 2), sleep('B', 2)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
看到這里你起碼可以簡單的講出來,因為顯然我們在傻傻的等。
我們沒有在開始等待的時候把程序暫停下來,然後在等待結束後繼續運行程序,我們一心一意的在等。
我們真的`time.sleep`了兩秒,而不是去做了兩秒其他的事情。
你有各種選擇,可以花式等待。我這里給你兩個最基本的例子:
* get請求
* 同步變為協程(線程池)
get請求
為了讓你更好的了解asyncio,我從最底層的socket開始寫一個協程的get請求給你。
為了模擬延時很大的網站,我在本地開了一個延時伺服器,這是伺服器程序。
import tornado.ioloopimport tornado.webfrom tornado.gen import coroutine, sleepclass MainHandler(tornado.web.RequestHandler):
@coroutine
def get(self, waitTime=3):
yield sleep(int(waitTime))
self.write('you have waited for %ss' % waitTime)if __name__ == "__main__":
application = tornado.web.Application([
('/([0-9])', MainHandler),
], debug=True)
application.listen(5000)
try:
tornado.ioloop.IOLoop.current().start()
except:
tornado.ioloop.IOLoop.current().stop()
記得打開了這個伺服器再運行下面的程序。
import socket, asyncio, timedata = 'GET /%s HTTP/1.1\r\n\r\n'loop = asyncio.get_event_loop()@asyncio.coroutinedef get(i):
future = asyncio.futures.Future(loop=loop)
s = socket.socket()
s.connect(('127.0.0.1', 5000))
s.sendall((data % i).encode('utf8'))
s.setblocking(False)
def callback(future):
future.set_result(s.recv(999).split(b'\r\n\r\n')[-1])
loop.add_reader(s.fileno(), callback, future)
r = yield from future
print('Return value: %s' % r)tasks = [get(3), get(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
同步變為協程(線程池)
這里拿sleep模擬耗時的程序,原理就是開了5個新的線程處理耗時程序。
當然實際的`asyncio.sleep`只需要告訴消息循環一定時間後叫醒我就好了。
import asyncio, sleep, [email protected] sleep(i):
executor = concurrent.futures.ThreadPoolExecutor(5)
future = asyncio.futures.wrap_future(executor.submit(time.sleep, i), loop=loop)
yield from future
print('Slept for %s seconds' % i)tasks = [sleep(3), sleep(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
③ Python 進程,線程,協程,鎖機制,你知多少
1.線程和進程:
線程是屬於進程的,線程運行在進程空間內,同一進程所產生的線程共享同一內存空間,當進程退出時該進程所產生的線程都會被強制退出並清除。線程可與屬於同一進程的其它線程共享進程所擁有的全部資源,但是其本身基本上不擁有系統資源,只擁有一點在運行中必不可少的信息(如程序計數器、一組寄存器和棧)。
2.線程、進程與協程:
線程和進程的操作是由程序觸發系統介面,最後的執行者是系統;協程的操作則是程序員
協程存在的意義:對於多線程應用,CPU通過切片的方式來切換線程間的執行,線程切換時需要耗時(保持狀態,下次繼續)。協程,則只使用一個線程,在一個線程中規定某個代碼塊執行順序。
協程的適用場景: 當程序中存在大量不需要CPU的操作時(IO),適用於協程;
④ python的協程是什麼和多線程有啥區別嗎
v 先簡要說下結論: 協同程序(coroutine)與多線程情況下的線程比較類似:有自己的堆棧,自己的局部變數,有自己的指令指針(IP,instruction pointer),但與其它協同程序共享全局變數等很多信息。 協程(協同程序): 同一時間只能執行某個協程...
⑤ 簡述python進程,線程和協程的區別
協程多與線程進行比較
1) 一個線程可以多個協程,一個進程也可以單獨擁有多個協程,這樣python中則能使用多核CPU。
2) 線程進程都是同步機制,而協程則是非同步
3) 協程能保留上一次調用時的狀態,每次過程重入時,就相當於進入上一次調用的狀態
⑥ Python 3.5 協程究竟是個啥
用 async def 可以定義得到 協程 。定義協程的另一種方式是通過 types.coroutine 修飾器 -- 從技術實現的角度來說就是添加了 CO_ITERABLE_COROUTINE 標記 -- 或者是 collections.abc.Coroutine 的子類。你只能通過基於生成器的定義來實現協程的暫停。
awaitable 對象 要麼是一個協程要麼是一個定義了 __await__() 方法的對象 -- 也就是 collections.abc.Awaitable -- 且 __await__() 必須返回一個不是協程的迭代器。 await 表達式基本上與 yield from 相同但只能接受awaitable對象(普通迭代器不行)。 async 定義的函數要麼包含 return 語句 -- 包括所有Pytohn函數預設的 return None -- 和/或者 await 表達式( yield 表達式不行)。 async 函數的限制確保你不會將基於生成器的協程與普通的生成器混合使用,因為對這兩種生成器的期望是非常不同的。
⑦ 詳解Python中的協程,為什麼說它的底層是生成器
協程又稱為是微線程,英文名是Coroutine。它和線程一樣可以調度,但是不同的是線程的啟動和調度需要通過操作系統來處理。並且線程的啟動和銷毀需要涉及一些操作系統的變數申請和銷毀處理,需要的時間比較長。而協程呢,它的調度和銷毀都是程序自己來控制的,因此它更加輕量級也更加靈活。
協程有這么多優點,自然也會有一些缺點,其中最大的缺點就是需要編程語言自己支持,否則的話需要開發者自己通過一些方法來實現協程。對於大部分語言來說,都不支持這一機制。go語言由於天然支持協程,並且支持得非常好,使得它廣受好評,短短幾年時間就迅速流行起來。
對於Python來說,本身就有著一個GIL這個巨大的先天問題。GIL是Python的全局鎖,在它的限制下一個Python進程同一時間只能同時執行一個線程,即使是在多核心的機器當中。這就大大影響了Python的性能,尤其是在CPU密集型的工作上。所以為了提升Python的性能,很多開發者想出了使用多進程+協程的方式。一開始是開發者自行實現的,後來在Python3.4的版本當中,官方也收入了這個功能,因此目前可以光明正大地說,Python是支持協程的語言了。
生成器(generator)
生成器我們也在之前的文章當中介紹過,為什麼我們介紹協程需要用到生成器呢,是因為Python的協程底層就是通過生成器來實現的。
通過生成器來實現協程的原因也很簡單,我們都知道協程需要切換掛起,而生成器當中有一個yield關鍵字,剛好可以實現這個功能。所以當初那些自己在Python當中開發協程功能的程序員都是通過生成器來實現的,我們想要理解Python當中協程的運用,就必須從最原始的生成器開始。
生成器我們很熟悉了,本質上就是帶有yield這個關鍵詞的函數。
async,await和future
從Python3.5版本開始,引入了async,await和future。我們來簡單說說它們各自的用途,其中async其實就是@asyncio.coroutine,用途是完全一樣的。同樣await代替的是yield from,意為等待另外一個協程結束。
我們用這兩個一改,上面的代碼就成了:
async def test(k):
n = 0
while n < k:
await asyncio.sleep(0.5)
print('n = {}'.format(n))
n += 1
由於我們加上了await,所以每次在列印之前都會等待0.5秒。我們把await換成yield from也是一樣的,只不過用await更加直觀也更加貼合協程的含義。
Future其實可以看成是一個信號量,我們創建一個全局的future,當一個協程執行完成之後,將結果存入這個future當中。其他的協程可以await future來實現阻塞。我們來看一個例子就明白了:
future = asyncio.Future()
async def test(k):
n = 0
while n < k:
await asyncio.sleep(0.5)
print('n = {}'.format(n))
n += 1
future.set_result('success')
async def log():
result = await future
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
log(),
test(5)
]))
loop.close()
在這個例子當中我們創建了兩個協程,第一個協程是每隔0.5秒print一個數字,在print完成之後把success寫入到future當中。第二個協程就是等待future當中的數據,之後print出來。
在loop當中我們要調度執行的不再是一個協程對象了而是兩個,所以我們用asyncio當中的wait將這兩個對象包起來。只有當wait當中的兩個對象執行結束,wait才會結束。loop等待的是wait的結束,而wait等待的是傳入其中的協程的結束,這就形成了一個依賴循環,等價於這兩個協程對象結束,loop才會結束。
總結
async並不只是可以用在函數上,事實上還有很多其他的用法,比如用在with語句上,用在for循環上等等。這些用法比較小眾,細節也很多,就不一一展開了,大家感興趣的可以自行去了解一下。
不知道大家在讀這篇文章的過程當中有沒有覺得有些費勁,如果有的話,其實是很正常的。原因也很簡單,因為Python原生是不支持協程這個概念的,所以在一開始設計的時候也沒有做這方面的准備,是後來覺得有必要才加入的。那麼作為後面加入的內容,必然會對原先的很多內容產生影響,尤其是協程藉助了之前生成器的概念來實現的,那麼必然會有很多耦合不清楚的情況。這也是這一塊的語法很亂,對初學者不友好的原因。
⑧ 在python中線程和協程的區別是什麼
在python中線程和協程的區別:1、一個線程可以擁有多個協程,這樣在python中就能使用多核CPU;2、線程是同步機制,而協程是非同步;3、 協程能保留上一次調用時的狀態,每次過程重入時,就相當於進入上一次調用的狀態。
一、首先我們來了解一下線程和協程的概念
1、線程
線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。
2、協程
協程是一種用戶態的輕量級線程,協程的調度完全由用戶控制。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變數,所以上下文的切換非常快。
二、協程與線程的比較
1) 一個線程可以擁有多個協程,一個進程也可以單獨擁有多個協程,這樣python中則能使用多核CPU。
2) 線程進程都是同步機制,而協程則是非同步。
3)協程能保留上一次調用時的狀態,每次過程重入時,就相當於進入上一次調用的狀態。
三、線程、協程在python中的使用
1、多線程一般是使用threading庫,完成一些IO密集型並發操作。多線程的優勢是切換快,資源消耗低,但一個線程掛掉則會影響到所有線程,所以不夠穩定。現實中使用線程池的場景會比較多,具體可參考《python線程池實現》。
2、協程一般是使用gevent庫,當然這個庫用起來比較麻煩,所以使用的並不是很多。相反,協程在tornado的運用就多得多了,使用協程讓tornado做到單線程非同步,據說還能解決C10K的問題。所以協程使用的地方最多的是在web應用上。
總結一下:
IO密集型一般使用多線程或者多進程,CPU密集型一般使用多進程,強調非阻塞非同步並發的一般都是使用協程,當然有時候也是需要多進程線程池結合的,或者是其他組合方式。
推薦課程:Python高級進階視頻教程