(2018-05-30.Python從Zero到One)8、(Tornado)異步與WebSockets__1.7.1認(rèn)識異步

7.1 認(rèn)識異步

1. 同步

我們用兩個函數(shù)來模擬兩個客戶端請求,并依次進行處理:

# coding:utf-8

def req_a():
    """模擬請求a"""
    print '開始處理請求req_a'
    print '完成處理請求req_a'

def req_b():
    """模擬請求b"""
    print '開始處理請求req_b'
    print '完成處理請求req_b'

def main():
    """模擬tornado框架,處理兩個請求"""
    req_a()
    req_b()

if __name__ == "__main__":
    main()

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

開始處理請求req_a
完成處理請求req_a
開始處理請求req_b
完成處理請求req_b
同步是按部就班的依次執(zhí)行,始終按照同一個步調(diào)執(zhí)行,上一個步驟未執(zhí)行完不會執(zhí)行下一步。

想一想,如果在處理請求req_a時需要執(zhí)行一個耗時的工作(如IO),其執(zhí)行過程如何?

# coding:utf-8

import time

def long_io():
    """模擬耗時IO操作"""
    print "開始執(zhí)行IO操作"
    time.sleep(5)
    print "完成IO操作"
    return "io result"

def req_a():
    print "開始處理請求req_a"
    ret = long_io()
    print "ret: %s" % ret
    print "完成處理請求req_a"

def req_b():
    print "開始處理請求req_b"
    print "完成處理請求req_b"

def main():
    req_a()
    req_b()

if __name__=="__main__":
    main()

執(zhí)行過程:

開始處理請求req_a
開始執(zhí)行IO操作
完成IO操作
完成處理請求req_a
開始處理請求req_b
完成處理請求req_b

在上面的測試中,我們看到耗時的操作會將代碼執(zhí)行阻塞住,即req_a未處理完req_b是無法執(zhí)行的。

我們怎么解決耗時操作阻塞代碼執(zhí)行?

2. 異步

對于耗時的過程,我們將其交給別人(如其另外一個線程)去執(zhí)行,而我們繼續(xù)往下處理,當(dāng)別人執(zhí)行完耗時操作后再將結(jié)果反饋給我們,這就是我們所說的異步。

我們用容易理解的線程機制來實現(xiàn)異步。

2.1 回調(diào)寫法實現(xiàn)原理

# coding:utf-8

import time
import thread

def long_io(callback):
    """將耗時的操作交給另一線程來處理"""
    def fun(cb): # 回調(diào)函數(shù)作為參數(shù)
        """耗時操作"""
        print "開始執(zhí)行IO操作"
        time.sleep(5)
        print "完成IO操作,并執(zhí)行回調(diào)函數(shù)"
        cb("io result")  # 執(zhí)行回調(diào)函數(shù)
    thread.start_new_thread(fun, (callback,))  # 開啟線程執(zhí)行耗時操作

def on_finish(ret):
    """回調(diào)函數(shù)"""
    print "開始執(zhí)行回調(diào)函數(shù)on_finish"
    print "ret: %s" % ret
    print "完成執(zhí)行回調(diào)函數(shù)on_finish"

def req_a():
    print "開始處理請求req_a" 
    long_io(on_finish)
    print "離開處理請求req_a"

def req_b():
    print "開始處理請求req_b"
    time.sleep(2) # 添加此句來突出顯示程序執(zhí)行的過程
    print "完成處理請求req_b"

def main():
    req_a()
    req_b()
    while 1: # 添加此句防止程序退出,保證線程可以執(zhí)行完
        pass

if __name__ == '__main__':
    main()

執(zhí)行過程:

開始處理請求req_a
離開處理請求req_a
開始處理請求req_b
開始執(zhí)行IO操作
完成處理請求req_b
完成IO操作,并執(zhí)行回調(diào)函數(shù)
開始執(zhí)行回調(diào)函數(shù)on_finish
ret: io result
完成執(zhí)行回調(diào)函數(shù)on_finish
異步的特點是程序存在多個步調(diào),即本屬于同一個過程的代碼可能在不同的步調(diào)上同時執(zhí)行。

2.2 協(xié)程寫法實現(xiàn)原理

在使用回調(diào)函數(shù)寫異步程序時,需將本屬于一個執(zhí)行邏輯(處理請求a)的代碼拆分成兩個函數(shù)req_a和on_finish,這與同步程序的寫法相差很大。而同步程序更便于理解業(yè)務(wù)邏輯,所以我們能否用同步代碼的寫法來編寫異步程序?

回想yield關(guān)鍵字的作用?
初始版本
# coding:utf-8

import time
import thread

gen = None # 全局生成器,供long_io使用

def long_io():
    def fun():
        print "開始執(zhí)行IO操作"
        global gen
        time.sleep(5)
        try:
            print "完成IO操作,并send結(jié)果喚醒掛起程序繼續(xù)執(zhí)行"
            gen.send("io result")  # 使用send返回結(jié)果并喚醒程序繼續(xù)執(zhí)行
        except StopIteration: # 捕獲生成器完成迭代,防止程序退出
            pass
    thread.start_new_thread(fun, ())

def req_a():
    print "開始處理請求req_a"
    ret = yield long_io()
    print "ret: %s" % ret
    print "完成處理請求req_a"

def req_b():
    print "開始處理請求req_b"
    time.sleep(2)
    print "完成處理請求req_b"

def main():
    global gen
    gen = req_a()
    gen.next() # 開啟生成器req_a的執(zhí)行
    req_b()
    while 1:
        pass

if __name__ == '__main__':
    main()

執(zhí)行過程:

開始處理請求req_a
開始處理請求req_b
開始執(zhí)行IO操作
完成處理請求req_b
完成IO操作,并send結(jié)果喚醒掛起程序繼續(xù)執(zhí)行
ret: io result
完成處理請求req_a
升級版本

我們在上面編寫出的版本雖然req_a的編寫方式很類似與同步代碼,但是在main中調(diào)用req_a的時候卻不能將其簡單的視為普通函數(shù),而是需要作為生成器對待。

現(xiàn)在,我們試圖嘗試修改,讓req_a與main的編寫都類似與同步代碼。

# coding:utf-8

import time
import thread

gen = None # 全局生成器,供long_io使用

def gen_coroutine(f):
    def wrapper(*args, **kwargs):
        global gen
        gen = f()
        gen.next()
    return wrapper

def long_io():
    def fun():
        print "開始執(zhí)行IO操作"
        global gen
        time.sleep(5)
        try:
            print "完成IO操作,并send結(jié)果喚醒掛起程序繼續(xù)執(zhí)行"
            gen.send("io result")  # 使用send返回結(jié)果并喚醒程序繼續(xù)執(zhí)行
        except StopIteration: # 捕獲生成器完成迭代,防止程序退出
            pass
    thread.start_new_thread(fun, ())

@gen_coroutine
def req_a():
    print "開始處理請求req_a"
    ret = yield long_io()
    print "ret: %s" % ret
    print "完成處理請求req_a"

def req_b():
    print "開始處理請求req_b"
    time.sleep(2)
    print "完成處理請求req_b"

def main():
    req_a()
    req_b()
    while 1:
        pass

if __name__ == '__main__':
    main()

執(zhí)行過程:

開始處理請求req_a
開始處理請求req_b
開始執(zhí)行IO操作
完成處理請求req_b
完成IO操作,并send結(jié)果喚醒掛起程序繼續(xù)執(zhí)行
ret: io result
完成處理請求req_a
最終版本

剛剛完成的版本依然不理想,因為存在一個全局變量gen來供long_io使用。我們現(xiàn)在再次改寫程序,消除全局變量gen。

# coding:utf-8

import time
import thread

def gen_coroutine(f):
    def wrapper(*args, **kwargs):
        gen_f = f()  # gen_f為生成器req_a
        r = gen_f.next()  # r為生成器long_io
        def fun(g):
            ret = g.next() # 執(zhí)行生成器long_io
            try:
                gen_f.send(ret) # 將結(jié)果返回給req_a并使其繼續(xù)執(zhí)行
            except StopIteration:
                pass
        thread.start_new_thread(fun, (r,))
    return wrapper

def long_io():
    print "開始執(zhí)行IO操作"
    time.sleep(5)
    print "完成IO操作,yield回操作結(jié)果"
    yield "io result"

@gen_coroutine
def req_a():
    print "開始處理請求req_a"
    ret = yield long_io()
    print "ret: %s" % ret
    print "完成處理請求req_a"

def req_b():
    print "開始處理請求req_b"
    time.sleep(2)
    print "完成處理請求req_b"

def main():
    req_a()
    req_b()
    while 1:
        pass

if __name__ == '__main__':
    main()

執(zhí)行過程:

開始處理請求req_a
開始處理請求req_b
開始執(zhí)行IO操作
完成處理請求req_b
完成IO操作,yield回操作結(jié)果
ret: io result
完成處理請求req_a
這個最終版本就是理解Tornado異步編程原理的最簡易模型,但是,Tornado實現(xiàn)異步的機制不是線程,而是epoll,即將異步過程交給epoll執(zhí)行并進行監(jiān)視回調(diào)。
需要注意的一點是,我們實現(xiàn)的版本嚴(yán)格意義上來說不能算是協(xié)程,因為兩個程序的掛起與喚醒是在兩個線程上實現(xiàn)的,而Tornado利用epoll來實現(xiàn)異步,程序的掛起與喚醒始終在一個線程上,由Tornado自己來調(diào)度,屬于真正意義上的協(xié)程。雖如此,并不妨礙我們理解Tornado異步編程的原理。

思考

Tornado里的異步就是協(xié)程,這句話對嗎?
Tornado中出現(xiàn)yield就是異步,這句話對嗎?
怎么理解yield將程序掛起?在Tornado中又如何理解yield掛起程序?qū)崿F(xiàn)異步?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 7.2 Tornado異步 因為epoll主要是用來解決網(wǎng)絡(luò)IO的并發(fā)問題,所以Tornado的異步編程也主要體現(xiàn)...
    lyh165閱讀 346評論 0 2
  • 1 什么是異步編程 通過學(xué)習(xí)相關(guān)概念,我們逐步解釋異步編程是什么。 1.1 阻塞 程序未得到所需計算資源時被掛起的...
    hugoren閱讀 2,762評論 2 10
  • 上篇 中篇 下篇 1 什么是異步編程 1.1 阻塞 程序未得到所需計算資源時被掛起的狀態(tài)。 程序在等待某個操作完成...
    秦時明星閱讀 1,180評論 0 3
  • 為人處事歡迎您的觀看,我是于老師。今天我給大家講的話題是“怎樣避免自我傷害”。 ▲ 自己還會傷害自己? 可能大家會...
    萬事通為人處事閱讀 293評論 0 2
  • 作者 云出岫無心 年關(guān)漸近。待我走出房門,去尋些年味來。 臘月廿七,上街。作主婦狀,背了花背兜,雙手抱在胸前,往人...
    井希柱閱讀 364評論 1 3

友情鏈接更多精彩內(nèi)容