python網(wǎng)絡(luò)-多任務(wù)實(shí)現(xiàn)之協(xié)程

一翠忠、協(xié)程

協(xié)程,又稱微線程乞榨,纖程秽之。英文名Coroutine。

協(xié)程不是進(jìn)程吃既,也不是線程考榨,它就是一個函數(shù),一個特殊的函數(shù)——可以在某個地方掛起鹦倚,并且可以重新在掛起處繼續(xù)運(yùn)行河质。所以說,協(xié)程與進(jìn)程震叙、線程相比愤诱,不是一個維度的概念。

一個進(jìn)程可以包含多個線程捐友,一個線程也可以包含多個協(xié)程淫半,也就是說,一個線程內(nèi)可以有多個那樣的特殊函數(shù)在運(yùn)行匣砖。但是有一點(diǎn)科吭,必須明確,一個線程內(nèi)的多個協(xié)程的運(yùn)行是串行的猴鲫。如果有多核CPU的話对人,多個進(jìn)程或一個進(jìn)程內(nèi)的多個線程是可以并行運(yùn)行的,但是一個線程內(nèi)的多個協(xié)程卻絕對串行的拂共,無論有多少個CPU(核)牺弄。這個比較好理解,畢竟協(xié)程雖然是一個特殊的函數(shù)宜狐,但仍然是一個函數(shù)势告。一個線程內(nèi)可以運(yùn)行多個函數(shù)蛇捌,但是這些函數(shù)都是串行運(yùn)行的。當(dāng)一個協(xié)程運(yùn)行時咱台,其他協(xié)程必須掛起络拌。

通俗的理解:在一個線程中的某個函數(shù),可以在任何地方保存當(dāng)前函數(shù)的一些臨時變量等信息回溺,然后切換到另外一個函數(shù)中執(zhí)行春贸,注意不是通過調(diào)用函數(shù)的方式做到的,并且切換的次數(shù)以及什么時候再切換到原來的函數(shù)都由開發(fā)者自己確定

學(xué)習(xí)Python中的小伙伴遗遵,需要學(xué)習(xí)資料的話萍恕,可以到我的微信公眾號:Python學(xué)習(xí)知識圈,后臺回復(fù):“01”车要,即可拿Python學(xué)習(xí)資料


二允粤、yield實(shí)現(xiàn)協(xié)程

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 import time
2
3 def A():
4 while True:
5 print("----A---")
6 yield
7 time.sleep(0.3)
8
9 def B(c):
10 while True:
11 print("----B---")
12 next(c)
13 time.sleep(0.3)
14
15 if name=='main':
16 a = A()
17 B(a)
</pre>

執(zhí)行結(jié)果

<pre class="prettyprint hljs lua" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">----B---
----A---
----B---
----A---
----B---
----A---
----B---
----A---
----B---
----A---
省略。屯蹦。维哈。
</pre>

代碼說明:

第17行:調(diào)用函數(shù)B,并把a(bǔ)傳遞進(jìn)去登澜。執(zhí)行打印B的代碼阔挠,代碼執(zhí)行到next(c)時,會調(diào)用函數(shù)A脑蠕,執(zhí)行打印A的代碼购撼,當(dāng)代碼實(shí)行帶第6行遇到y(tǒng)ield的實(shí)行,該協(xié)程進(jìn)入等待狀態(tài)谴仙,回到原來next(c)處繼續(xù)執(zhí)行迂求, 從而實(shí)現(xiàn)多協(xié)程的切換,通過yield關(guān)鍵字晃跺。

三揩局、greenlet

1、greenlet實(shí)現(xiàn)多任務(wù)協(xié)程

為了更好使用協(xié)程來完成多任務(wù)掀虎,python中的greenlet模塊對其封裝凌盯,從而使得切換任務(wù)變的更加簡單,在使用前先要確保greenlet模塊安裝

使用如下命令安裝greenlet模塊:

<pre class="hljs nginx" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">sudo pip install greenlet
</pre>

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#coding = utf-8
from greenlet import greenlet
def test1():
print("1")
gr2.switch()
print("2")

def test2():
print("3")
gr1.switch()
print("4")

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
</pre>

運(yùn)行結(jié)果:

當(dāng)創(chuàng)建一個greenlet時烹玉,首先初始化一個空的棧驰怎, switch到這個棧的時候,會運(yùn)行在greenlet構(gòu)造時傳入的函數(shù)(首先在test1中打印 1)二打, 如果在這個函數(shù)(test1)中switch到其他協(xié)程(到了test2 打印3)县忌,那么該協(xié)程會被掛起,等到切換回來(在test1切換回來 打印2)。當(dāng)這個協(xié)程對應(yīng)函數(shù)執(zhí)行完畢症杏,那么這個協(xié)程就變成dead狀態(tài)装获。

注意 上面沒有打印test2的最后一行輸出 4,因為在test2中切換到gr1之后掛起鸳慈,但是沒有地方再切換回來饱溢。

2喧伞、greenlet的模塊與類

我們首先看一下greenlet這個module里面的屬性

<pre class="prettyprint hljs ruby" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">>>> import greenlet

dir(greenlet)
['GREENLET_USE_GC', 'GREENLET_USE_TRACING', 'GreenletExit', '_C_API', 'doc', 'file', 'loader', 'name', 'package', 'spec', 'version', 'error', 'getcurrent', 'gettrace', 'greenlet', 'settrace']
</pre>

其中走芋,比較重要的是getcurrent(), 類greenlet潘鲫、異常類GreenletExit翁逞。

getcurrent()返回當(dāng)前的greenlet實(shí)例;

GreenletExit:是一個特殊的異常溉仑,當(dāng)觸發(fā)了這個異常的時候挖函,即使不處理,也不會拋到其parent(后面會提到協(xié)程中對返回值或者異常的處理)

然后我們再來看看greenlet.greenlet這個類:

<pre class="prettyprint hljs ruby" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">>>>dir(greenlet.greenlet)
['GreenletExit', 'bool', 'class', 'delattr', 'dict', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'getstate', 'gt', 'hash', 'init', 'init_subclass', 'le', 'lt', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', 'setattr', 'sizeof', 'str', 'subclasshook', '_stack_saved', 'dead', 'error', 'getcurrent', 'gettrace', 'gr_frame', 'parent', 'run', 'settrace', 'switch', 'throw']
</pre>

比較重要的幾個屬性:

run:當(dāng)greenlet啟動的時候會調(diào)用到這個callable浊竟,如果我們需要繼承g(shù)reenlet.greenlet時怨喘,需要重寫該方法

switch:前面已經(jīng)介紹過了,在greenlet之間切換

parent:可讀寫屬性振定,后面介紹

dead:如果greenlet執(zhí)行結(jié)束必怜,那么該屬性為true

throw:切換到指定greenlet后立即跑出異常

文章后面提到的greenlet大多都是指greenlet.greenlet這個class,請注意區(qū)別

對于greenlet后频,最常用的寫法是 x = gr.switch(y)梳庆。 這句話的意思是切換到gr,傳入?yún)?shù)y卑惜。當(dāng)從其他協(xié)程( 不一定是這個gr )切換回來的時候膏执,將值付給x。

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import greenlet

def test1(x, y):
z = gr2.switch(x + y)
print("test1:%s" % z)

def test2(a):
print('test2:%s' % a)
gr1.switch(10)

gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)
print(gr1.switch("Hello", "World"))
</pre>

運(yùn)行結(jié)果為:

<pre class="hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">test2:HelloWorld
test1:10
None
</pre>

上面的例子露久,第10行從main greenlet切換到了gr1更米,test1第3行切換到了gs2,然后gr1掛起毫痕,第7行從gr2切回gr1時征峦,將值(10)返回值給了 z。

3镇草、greenlet生命周期

文章開始的地方提到第一個例子中的gr2其實(shí)并沒有正常結(jié)束眶痰,我們可以借用greenlet.dead這個屬性來查看

運(yùn)行結(jié)果為:

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 import greenlet
2
3
4 def test1():
5 gr2.switch(1)
6 print("test1: finished")
7
8
9 def test2(x):
10 print("test2:first %s" % x)
11 gr1.switch()
12 print("test2:back")
13
14 gr1 = greenlet.greenlet(test1)
15 gr2 = greenlet.greenlet(test2)
16 gr1.switch()
17 print("gr1 is dead? : %s, gr2 is dead? :%s" % (gr1.dead, gr2.dead))
18 gr2.switch()
19 print("gr1 is dead? : %s, gr2 is dead? :%s" % (gr1.dead, gr2.dead))
</pre>

運(yùn)行結(jié)果為:

<pre class="prettyprint hljs vbscript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">test2:first 1
test1: finished
gr1 is dead? : True, gr2 is dead? :False
test2:back
gr1 is dead? : True, gr2 is dead? :True
</pre>

只有當(dāng)協(xié) 程對應(yīng)的函數(shù)執(zhí)行完畢,協(xié)程才會die 梯啤,所以第一次Check的時候gr2并沒有die竖伯,因為第12行切換出去了就沒切回來。在main中再switch到gr2的時候, 執(zhí)行后面的邏輯七婴,gr2 die

4祟偷、greenlet注意事項

使用greenlet需要注意一下三點(diǎn):

第一:greenlet創(chuàng)生之后,一定要結(jié)束打厘,不能switch出去就不回來了修肠,否則容易造成內(nèi)存泄露

第二:python中每個線程都有自己的main greenlet及其對應(yīng)的sub-greenlet ,不能線程之間的greenlet是不能相互切換的

第三:不能存在循環(huán)引用户盯,這個是官方文檔明確說明

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 from greenlet import greenlet, GreenletExit
2 huge = []
3 def show_leak():
4 def test1():
5 gr2.switch()
6
7 def test2():
8 huge.extend([x* x for x in range(100)])
9 gr1.switch()
10 print 'finish switch del huge'
11 del huge[:]
12
13 gr1 = greenlet(test1)
14 gr2 = greenlet(test2)
15 gr1.switch()
16 gr1 = gr2 = None
17 print 'length of huge is zero ? %s' % len(huge)
18
19 if name == 'main':
20 show_leak()
</pre>

在test2函數(shù)中 第11行嵌施,我們將huge清空,然后再第16行將gr1莽鸭、gr2的引用計數(shù)降到了0吗伤。但運(yùn)行結(jié)果告訴我們,第11行并沒有執(zhí)行硫眨,所以如果一個協(xié)程沒有正常結(jié)束是很危險的足淆,往往不符合程序員的預(yù)期。greenlet提供了解決這個問題的辦法礁阁,官網(wǎng)文檔提到:如果一個greenlet實(shí)例的引用計數(shù)變成0巧号,那么會在上次掛起的地方拋出GreenletExit異常,這就使得我們可以通過try ... finally 處理資源泄露的情況姥闭。如下面的代碼:

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1 from greenlet import greenlet, GreenletExit
2 huge = []
3 def show_leak():
4 def test1():
5 gr2.switch()
6
7 def test2():
8 huge.extend([x* x for x in range(100)])
9 try:
10 gr1.switch()
11 finally:
12 print 'finish switch del huge'
13 del huge[:]
14
15 gr1 = greenlet(test1)
16 gr2 = greenlet(test2)
17 gr1.switch()
18 gr1 = gr2 = None
19 print 'length of huge is zero ? %s' % len(huge)
20
21 if name == 'main':
22 show_leak()
</pre>

上述代碼的switch流程:main greenlet --> gr1 --> gr2 --> gr1 --> main greenlet, 很明顯gr2沒有正常結(jié)束(在第10行刮起了)丹鸿。第18行之后gr1,gr2的引用計數(shù)都變成0,那么會在第10行拋出GreenletExit異常泣栈,因此finally語句有機(jī)會執(zhí)行卜高。同時,在文章開始介紹Greenlet module的時候也提到了南片,GreenletExit這個異常并不會拋出到parent掺涛,所以main greenlet也不會出異常。

四疼进、gevent

greenlet已經(jīng)實(shí)現(xiàn)了協(xié)程薪缆,但是這個還的人工切換,是不是覺得太麻煩了伞广,不要捉急拣帽,python還有一個比greenlet更強(qiáng)大的并且能夠自動切換任務(wù)的模塊 gevent

其原理是當(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在運(yùn)行,而不是等待IO

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import gevent

def f():
for i in range(5):
print("%s:%d"%(gevent.getcurrent(),i))

g1 = gevent.spawn(f)
g2 = gevent.spawn(f)
g3 = gevent.spawn(f)
g1.join()
g2.join()
g3.join()
</pre>

運(yùn)行結(jié)果為:

<pre class="prettyprint hljs go" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><Greenlet at 0x1ba533f9598: f(5)>:0
<Greenlet at 0x1ba533f9598: f(5)>:1
<Greenlet at 0x1ba533f9598: f(5)>:2
<Greenlet at 0x1ba533f9598: f(5)>:3
<Greenlet at 0x1ba533f9598: f(5)>:4
<Greenlet at 0x1ba533f97b8: f(5)>:0
<Greenlet at 0x1ba533f97b8: f(5)>:1
<Greenlet at 0x1ba533f97b8: f(5)>:2
<Greenlet at 0x1ba533f97b8: f(5)>:3
<Greenlet at 0x1ba533f97b8: f(5)>:4
<Greenlet at 0x1ba533f99d8: f(5)>:0
<Greenlet at 0x1ba533f99d8: f(5)>:1
<Greenlet at 0x1ba533f99d8: f(5)>:2
<Greenlet at 0x1ba533f99d8: f(5)>:3
<Greenlet at 0x1ba533f99d8: f(5)>:4
</pre>

可以看到癣朗,3個greenlet是依次運(yùn)行而不是交替運(yùn)行

gevent的切換執(zhí)行

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import gevent

def f():
for i in range(5):
print("%s:%d"%(gevent.getcurrent(),i))
gevent.sleep(0)

g1=gevent.spawn(f)
g2=gevent.spawn(f)
g3=gevent.spawn(f)
g1.join()
g2.join()
g3.join()
</pre>

執(zhí)行結(jié)果為:

<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><Greenlet at 0x20a5e719598: f>:0
<Greenlet at 0x20a5e7197b8: f>:0
<Greenlet at 0x20a5e7199d8: f>:0
<Greenlet at 0x20a5e719598: f>:1
<Greenlet at 0x20a5e7197b8: f>:1
<Greenlet at 0x20a5e7199d8: f>:1
<Greenlet at 0x20a5e719598: f>:2
<Greenlet at 0x20a5e7197b8: f>:2
<Greenlet at 0x20a5e7199d8: f>:2
<Greenlet at 0x20a5e719598: f>:3
<Greenlet at 0x20a5e7197b8: f>:3
<Greenlet at 0x20a5e7199d8: f>:3
<Greenlet at 0x20a5e719598: f>:4
<Greenlet at 0x20a5e7197b8: f>:4
<Greenlet at 0x20a5e7199d8: f>:4
</pre>

3個greenlet交替運(yùn)行

gevent.spawn 啟動協(xié)程拾因,參數(shù)為函數(shù)名稱,參數(shù)名稱

3旷余、gevent并發(fā)下載器

monkey可以使一些阻塞的模塊變得不阻塞绢记,機(jī)制:遇到IO操作則自動切換,手動切換可以用gevent.sleep(0)

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">from gevent import monkey
import gevent
import urllib.request

有I/O時需要這一句,如果沒有這句話就會有阻塞狀態(tài)荣暮,加上就沒有阻塞

monkey.patch_all()

def myDownLoad(url):
print("GET:%s"%url)
resp = urllib.request.urlopen(url)
data = resp.read()
print("%d bytes received from %s"%(len(data),url))

gevent.joinall((
gevent.spawn(myDownLoad,"http://www.baidu.com/"),
gevent.spawn(myDownLoad,"https://apple.com"),
gevent.spawn(myDownLoad,"https://www.cnblogs.com/Se7eN-HOU/")
))
</pre>

運(yùn)行結(jié)果為:

<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">GET:http://www.baidu.com/
GET:https://apple.com
GET:https://www.cnblogs.com/Se7eN-HOU/
153390 bytes received from http://www.baidu.com/
18880 bytes received from https://www.cnblogs.com/Se7eN-HOU/
58865 bytes received from https://apple.com
</pre>

從上能夠看到是先發(fā)送的獲取baidu的相關(guān)信息庭惜,然后依次是apple,cnblogs但是收到數(shù)據(jù)的先后順序不一定與發(fā)送順序相同罩驻,這也就體現(xiàn)出了異步穗酥,即不確定什么時候會收到數(shù)據(jù),順序不一定.

上面如果沒有下面這句代碼惠遏,

<pre class="prettyprint hljs makefile" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#有I/O時需要這一句,如果沒有這句話就會有阻塞狀態(tài)砾跃,加上就沒有阻塞
monkey.patch_all()
</pre>

執(zhí)行結(jié)果如下

<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">GET:http://www.baidu.com/
153378 bytes received from http://www.baidu.com/
GET:https://apple.com
58865 bytes received from https://apple.com
GET:https://www.cnblogs.com/Se7eN-HOU/
18880 bytes received from https://www.cnblogs.com/Se7eN-HOU/
</pre>

每請求一個網(wǎng)站就會等著請求完畢了在執(zhí)行第二個,在請求的過程中节吮,網(wǎng)速慢等待的狀態(tài)就是在阻塞抽高。

五、asyncio

我們都知道透绩,現(xiàn)在的服務(wù)器開發(fā)對于IO調(diào)度的優(yōu)先級控制權(quán)已經(jīng)不再依靠系統(tǒng)翘骂,都希望采用協(xié)程的方式實(shí)現(xiàn)高效的并發(fā)任務(wù),如js帚豪、lua等在異步協(xié)程方面都做的很強(qiáng)大碳竟。

Python 在3.4版本也加入了協(xié)程的概念,并在3.5確定了基本完善的語法和實(shí)現(xiàn)方式狸臣。同時3.6也對其進(jìn)行了如解除了await和yield在同一個函數(shù)體限制等相關(guān)的優(yōu)化莹桅。

event_loop 事件循環(huán):程序開啟一個無限的循環(huán),程序員會把一些函數(shù)注冊到事件循環(huán)上烛亦。當(dāng)滿足事件發(fā)生的時候诈泼,調(diào)用相應(yīng)的協(xié)程函數(shù)。

coroutine 協(xié)程:協(xié)程對象煤禽,指一個使用async關(guān)鍵字定義的函數(shù)铐达,它的調(diào)用不會立即執(zhí)行函數(shù),而是會返回一個協(xié)程對象檬果。協(xié)程對象需要注冊到事件循環(huán)瓮孙,由事件循環(huán)調(diào)用贾节。

task 任務(wù):一個協(xié)程對象就是一個原生可以掛起的函數(shù),任務(wù)則是對協(xié)程進(jìn)一步封裝衷畦,其中包含任務(wù)的各種狀態(tài)栗涂。

future: 代表將來執(zhí)行或沒有執(zhí)行的任務(wù)的結(jié)果。它和task上沒有本質(zhì)的區(qū)別

async/await 關(guān)鍵字:python3.5 用于定義協(xié)程的關(guān)鍵字祈争,async定義一個協(xié)程斤程,await用于掛起阻塞的異步調(diào)用接口。

1菩混、創(chuàng)建協(xié)程

首先定義一個協(xié)程忿墅,在def前加入async聲明,就可以定義一個協(xié)程函數(shù)沮峡。

一個協(xié)程函數(shù)不能直接調(diào)用運(yùn)行疚脐,只能把協(xié)程加入到事件循環(huán)loop中。asyncio.get_event_loop方法可以創(chuàng)建一個事件循環(huán)邢疙,然后使用run_until_complete將協(xié)程注冊到事件循環(huán)棍弄,并啟動事件循環(huán)。

例如:

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio

async def fun():
print("---協(xié)程中---")

def main():
print("---主線程中---")

loop = asyncio.get_event_loop()
loop.run_until_complete(fun())

if name == "main":
main()
</pre>

運(yùn)行結(jié)果:

<pre class="hljs lua" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">---主線程中---
---協(xié)程中---
</pre>

二疟游、任務(wù)對象task

協(xié)程對象不能直接運(yùn)行呼畸,在注冊事件循環(huán)的時候,其實(shí)是run_until_complete方法將協(xié)程包裝成為了一個任務(wù)(task)對象颁虐。所謂task對象是Future類的子類蛮原。保存了協(xié)程運(yùn)行后的狀態(tài),用于未來獲取協(xié)程的結(jié)果另绩。

例如:

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio

async def fun():
print("---協(xié)程中---")
return "Se7eN_HOU"

def main():
print("---主線程中---")

loop = asyncio.get_event_loop()
#創(chuàng)建task
task = loop.create_task(fun())
print(task)
loop.run_until_complete(task)
print(task)

if name == "main":
main()
</pre>

運(yùn)行結(jié)果為:

<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">---主線程中---
<Task pending coro=<fun() running at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:4>>
---協(xié)程中---
<Task finished coro=<fun() done, defined at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:4> result='Se7eN_HOU'>
</pre>

創(chuàng)建task后儒陨,task在加入事件循環(huán)之前是pending狀態(tài),因為fun()中沒有耗時的阻塞操作笋籽,task很快就執(zhí)行完畢了蹦漠。后面打印的finished狀態(tài)。

asyncio.ensure_future 和 loop.create_task都可以創(chuàng)建一個task干签,run_until_complete的參數(shù)是一個futrue對象津辩。

三、綁定回調(diào)

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio

協(xié)程

async def fun():
print("---協(xié)程中---")
return "Se7eN_HOU"

協(xié)程的回調(diào)函數(shù)

def callback(future):
#future.result是協(xié)程的返回值
print("callBack:%s"%future.result())

def main():
print("---主線程中---")
#創(chuàng)建loop回路
loop = asyncio.get_event_loop()
#創(chuàng)建task
task = loop.create_task(fun())
#調(diào)用回調(diào)函數(shù)
task.add_done_callback(callback)
print(task)
loop.run_until_complete(task)
print(task)

if name == "main":
main()
</pre>

運(yùn)行結(jié)果為:

<pre class="prettyprint hljs groovy" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">---主線程中---
<Task pending coro=<fun() running at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:4> cb=[callback() at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:9]>
---協(xié)程中---
callBack:Se7eN_HOU
<Task finished coro=<fun() done, defined at C:/Users/Se7eN_HOU/PycharmProjects/PythonLesson/test.py:4> result='Se7eN_HOU'>
</pre>

也可以使用ensure_future獲取返回值

例如:

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio

協(xié)程

async def fun():
print("---協(xié)程中---")
return "Se7eN_HOU"

協(xié)程的回調(diào)函數(shù)

def callback(future):

#future.result是協(xié)程的返回值
#print("callBack:%s"%future.result())

def main():
#創(chuàng)建loop回路
loop = asyncio.get_event_loop()
#創(chuàng)建task
#task = loop.create_task(fun())
#調(diào)用回調(diào)函數(shù)
#task.add_done_callback(callback)
task = asyncio.ensure_future(fun())
loop.run_until_complete(task)
print("fun函數(shù)的返回值是:%s"%format(task.result()))

if name == "main":
main()
</pre>

運(yùn)行結(jié)果為:

<pre class="hljs kotlin" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">---協(xié)程中---
fun函數(shù)的返回值是:Se7eN_HOU
</pre>

四容劳、await阻塞喘沿,執(zhí)行并發(fā)

使用async可以定義協(xié)程對象,使用await可以針對耗時的操作進(jìn)行掛起竭贩,就像生成器里的yield一樣蚜印,函數(shù)讓出控制權(quán)。協(xié)程遇到await留量,事件循環(huán)將會掛起該協(xié)程窄赋,執(zhí)行別的協(xié)程哟冬,直到其他的協(xié)程也掛起或者執(zhí)行完畢,再進(jìn)行下一個協(xié)程的執(zhí)行忆绰。

耗時的操作一般是一些IO操作浩峡,例如網(wǎng)絡(luò)請求,文件讀取等错敢。我們使用asyncio.sleep函數(shù)來模擬IO操作翰灾。協(xié)程的目的也是讓這些IO操作異步化。

例如:

<pre class="prettyprint hljs python" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import asyncio

async def test1():
print("---1---")
await asyncio.sleep(5)
print("---2---")

async def test2():
print("---3---")
await asyncio.sleep(1)
print("---4---")

async def test3():
print("---5---")
await asyncio.sleep(3)
print("---6---")

def main():
loop = asyncio.get_event_loop()
print("begin")

t1 = test1()
t2 = test2()
t3 = test3()
tasks1 = [t1,t2,t3]

loop.run_until_complete(asyncio.wait(tasks1))
print("end")
loop.close()

if name=="main":
main()
</pre>

運(yùn)行結(jié)果為:

<pre class="hljs sql" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">begin
---3---
---1---
---5---
---4---
---6---
---2---
end</pre>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稚茅,一起剝皮案震驚了整個濱河市纸淮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌亚享,老刑警劉巖咽块,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異欺税,居然都是意外死亡侈沪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門魄衅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峭竣,“玉大人,你說我怎么就攤上這事晃虫。” “怎么了扣墩?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵哲银,是天一觀的道長。 經(jīng)常有香客問我呻惕,道長荆责,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任亚脆,我火速辦了婚禮做院,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘濒持。我一直安慰自己键耕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布柑营。 她就那樣靜靜地躺著屈雄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪官套。 梳的紋絲不亂的頭發(fā)上酒奶,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天蚁孔,我揣著相機(jī)與錄音,去河邊找鬼惋嚎。 笑死杠氢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的另伍。 我是一名探鬼主播修然,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼质况!你這毒婦竟也來了愕宋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤结榄,失蹤者是張志新(化名)和其女友劉穎中贝,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體臼朗,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邻寿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了视哑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绣否。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挡毅,靈堂內(nèi)的尸體忽然破棺而出蒜撮,到底是詐尸還是另有隱情,我是刑警寧澤跪呈,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布段磨,位于F島的核電站,受9級特大地震影響耗绿,放射性物質(zhì)發(fā)生泄漏苹支。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一误阻、第九天 我趴在偏房一處隱蔽的房頂上張望债蜜。 院中可真熱鬧,春花似錦究反、人聲如沸寻定。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽特姐。三九已至,卻和暖如春黍氮,著一層夾襖步出監(jiān)牢的瞬間唐含,已是汗流浹背浅浮。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捷枯,地道東北人滚秩。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像淮捆,于是被迫代替她去往敵國和親郁油。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容