<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                到上一節結束,其實讀者已經能夠做一個網站了,但是,僅僅用前面的技術來做的網站,僅能算一個小網站,在[《為做網站而準備》](https://github.com/qiwsir/StarterLearningPython/blob/master/301.md)中,說明之所以選tornado,就是因為它能夠解決c10k問題,即能夠實現大用戶量訪問。 要實現大用戶量訪問,必須要做的就是:異步。除非你是很土的土豪。 ## [](https://github.com/qiwsir/StarterLearningPython/blob/master/309.md#相關概念)相關概念 ### [](https://github.com/qiwsir/StarterLearningPython/blob/master/309.md#同步和異步)同步和異步 有不少資料對這兩個概念做了不同角度和層面的解釋。在我來看,一個最典型的例子就是打電話和發短信。 * 打電話就是同步。張三給李四打電話,張三說:“是李四嗎?”。當這個信息被張三發出,提交給李四,就等待李四的響應(一般會聽到“是”,或者“不是”),只有得到了李四返回的信息之后,才能進行后續的信息傳送。 * 發短信是異步。張三給李四發短信,編輯了一句話“今晚一起看老齊的零基礎學python”,發送給李四。李四或許馬上回復,或許過一段時間,這段時間多長也不定,才回復。總之,李四不管什么時候回復,張三會以聽到短信鈴聲為提示查看短信。 以上方式理解“同步”和“異步”不是很精準,有些地方或有牽強。要嚴格理解,需要用嚴格一點的定義表述(以下表述參照了[知乎](http://www.zhihu.com/question/19732473)上的回答): > 同步和異步關注的是消息通信機制 (synchronous communication/ asynchronous communication) > > 所謂同步,就是在發出一個“調用”時,在沒有得到結果之前,該“調用”就不返回。但是一旦調用返回,就得到返回值了。 換句話說,就是由“調用者”主動等待這個“調用”的結果。 > > 而異步則是相反,“調用”在發出之后,這個調用就直接返回了,所以沒有返回結果。換句話說,當一個異步過程調用發出后,調用者不會立刻得到結果。而是在“調用”發出后,“被調用者”通過狀態、通知來通知調用者,或通過回調函數處理這個調用。 可能還是前面的打電話和發短信更好理解。 ### [](https://github.com/qiwsir/StarterLearningPython/blob/master/309.md#阻塞和非阻塞)阻塞和非阻塞 “阻塞和非阻塞”與“同步和異步”常常被換為一談,其實它們之間還是有差別的。如果按照一個“差不多”先生的思維方法,你也可以不那么深究它們之間的學理上的差距,反正在你的程序中,會使用就可以了。不過,必要的嚴謹還是需要的,特別是我寫這個教程,要裝扮的讓別人看來自己懂,于是就再引用[知乎](http://www.zhihu.com/question/19732473)上的說明(我個人認為,別人已經做的挺好的東西,就別重復勞動了,“拿來主義”,也不錯。或許你說我抄襲和山寨,但是我明確告訴你來源了): > 阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態. > > 阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之后才會返回。非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程。 按照這個說明,發短信就是顯然的非阻塞,發出去一條短信之后,你利用手機還可以干別的,乃至于再發一條“老齊的課程沒意思,還是看PHP刺激”也是可以的。 關于這兩組基本概念的辨析,不是本教程的重點,讀者可以參閱這篇文章:[http://www.cppblog.com/converse/archive/2009/05/13/82879.html](http://www.cppblog.com/converse/archive/2009/05/13/82879.html),文章作者做了細致入微的辨析。 ## [](https://github.com/qiwsir/StarterLearningPython/blob/master/309.md#tornado的同步)tornado的同步 此前,在tornado基礎上已經完成的web,就是同步的、阻塞的。為了更明顯的感受這點,不妨這樣試一試。 在handlers文件夾中建立一個文件,命名為sleep.py ~~~ #!/usr/bin/env python # coding=utf-8 from base import BaseHandler import time class SleepHandler(BaseHandler): def get(self): time.sleep(17) self.render("sleep.html") class SeeHandler(BaseHandler): def get(self): self.render("see.html") ~~~ 其它的事情,如果讀者對我在[《用tornado做網站(1)》](https://github.com/qiwsir/StarterLearningPython/blob/master/303.md)中所講述的網站框架熟悉,應該知道如何做了,不熟悉,請回頭復習。 sleep.html和see.html是兩個簡單的模板,內容可以自己寫。別忘記修改url.py中的目錄。 然后的測試稍微復雜一點點,就是打開瀏覽器之后,打開兩個標簽,分別在兩個標簽中輸入`localhost:8000/sleep`(記為標簽1)和`localhost:8000/see`(記為標簽2),注意我用的是8000端口。輸入之后先不要點擊回車去訪問。做好準備,記住切換標簽可以用“ctrl-tab”組合鍵。 1. 執行標簽1,讓它訪問網站; 2. 馬上切換到標簽2,訪問網址。 3. 注意觀察,兩個標簽頁面,是不是都在顯示正在訪問,請等待。 4. 當標簽1不呈現等待提示(比如一個正在轉的圓圈)時,標簽2的表現如何?幾乎同時也訪問成功了。 建議讀者修改sleep.py中的time.sleep(17)這個值,多試試。很好玩的吧。 當然,這是比較笨拙的方法,本來是可以通過測試工具完成上述操作比較的。怎奈要用別的工具,還要進行介紹,又多了一個分散精力的東西,故用如此笨拙的方法,權當有一個體會。 ## [](https://github.com/qiwsir/StarterLearningPython/blob/master/309.md#異步設置)異步設置 tornado本來就是一個異步的服務框架,體現在tornado的服務器和客戶端的網絡交互的異步上,起作用的是tornado.ioloop.IOLoop。但是如果的客戶端請求服務器之后,在執行某個方法的時候,比如上面的代碼中執行get()方法的時候,遇到了`time.sleep(17)`這個需要執行時間比較長的操作,耗費時間,就會使整個tornado服務器的性能受限了。 為了解決這個問題,tornado提供了一套異步機制,就是異步裝飾器`@tornado.web.asynchronous`: ~~~ #!/usr/bin/env python # coding=utf-8 import tornado.web from base import BaseHandler import time class SleepHandler(BaseHandler): @tornado.web.asynchronous def get(self): tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 17, callback=self.on_response) def on_response(self): self.render("sleep.html") self.finish() ~~~ 將sleep.py的代碼如上述一樣改造,即在get()方法前面增加了裝飾器`@tornado.web.asynchronous`,它的作用在于將tornado服務器本身默認的設置`_auto_fininsh`值修改為false。如果不用這個裝飾器,客戶端訪問服務器的get()方法并得到返回值之后,兩只之間的連接就斷開了,但是用了`@tornado.web.asynchronous`之后,這個連接就不關閉,直到執行了`self.finish()`才關閉這個連接。 `tornado.ioloop.IOLoop.instance().add_timeout()`也是一個實現異步的函數,`time.time()+17`是給前面函數提供一個參數,這樣實現了相當于`time.sleep(17)`的功能,不過,還沒有完成,當這個操作完成之后,就執行回調函數`on_response()`中的`self.render("sleep.html")`,并關閉連接`self.finish()`。 過程清楚了。所謂異步,就是要解決原來的`time.sleep(17)`造成的服務器處理時間長,性能下降的問題。解決方法如上描述。 讀者看這個代碼,或許感覺有點不是很舒服。如果有這么一點感覺,是正常的。因為它里面除了裝飾器之外,用到了一個回調函數,它讓代碼的邏輯不是平鋪下去,而是被分割為了兩段。第一段是`tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 17, callback=self.on_response)`,用`callback=self.on_response`來使用回調函數,并沒有如同改造之前直接`self.render("sleep.html")`;第二段是回調函數on_response(self)`,要在這個函數里面執行`self.render("sleep.html")`,并且以`self.finish()`結尾以關閉連接。 這還是執行簡單邏輯,如果復雜了,不斷地要進行“回調”,無法讓邏輯順利延續,那面會“眩暈”了。這種現象被業界成為“代碼邏輯拆分”,打破了原有邏輯的順序性。為了讓代碼邏輯不至于被拆分的七零八落,于是就出現了另外一種常用的方法: ~~~ #!/usr/bin/env python # coding=utf-8 import tornado.web import tornado.gen from base import BaseHandler import time class SleepHandler(tornado.web.RequestHandler): @tornado.gen.coroutine def get(self): yield tornado.gen.Task(tornado.ioloop.IOLoop.instance().add_timeout, time.time() + 17) #yield tornado.gen.sleep(17) self.render("sleep.html") ~~~ 從整體上看,這段代碼避免了回調函數,看著順利多了。 再看細節部分。 首先使用的是`@tornado.gen.coroutine`裝飾器,所以要在前面有`import tornado.gen`。跟這個裝飾器類似的是`@tornado.gen.engine`裝飾器,兩者功能類似,有一點細微差別。請閱讀[官方對此的解釋](http://www.tornadoweb.org/en/stable/gen.html): > This decorator(指engine) is similar to coroutine, except it does not return a Future and the callback argument is not treated specially. `@tornado.gen.engine`是古時候用的,現在我們都使用`@tornado.gen.corroutine`了,這個是在tornado 3.0以后開始。在網上查閱資料的時候,會遇到一些使用`@tornado.gen.engine`的,但是在你使用或者借鑒代碼的時候,就勇敢地將其修改為`@tornado.gen.coroutine`好了。有了這個裝飾器,就能夠控制下面的生成器的流程了。 然后就看到get()方法里面的yield了,這是一個生成器(參閱本教程[《生成器》](https://github.com/qiwsir/StarterLearningPython/blob/master/215.md))。`yield tornado.gen.Task(tornado.ioloop.IOLoop.instance().add_timeout, time.time() + 17)`的執行過程,應該先看括號里面,跟前面的一樣,是來替代`time.sleep(17)`的,然后是`tornado.gen.Task()`方法,其作用是“Adapts a callback-based asynchronous function for use in coroutines.”(由于怕翻譯后遺漏信息,引用[原文](http://tornado.readthedocs.org/en/latest/gen.html))。返回后,最后使用yield得到了一個生成器,先把流程掛起,等完全完畢,再喚醒繼續執行。要提醒讀者,生成器都是異步的。 其實,上面啰嗦一對,可以用代碼中注釋了的一句話來代替`yield tornado.gen.sleep(17)`,之所以擴所,就是為了順便看到`tornado.gen.Task()`方法,因為如果讀者在看古老的代碼時候,會遇到。但是,后面你寫的時候,就不要那么啰嗦了,請用`yield tornado.gen.sleep()`。 至此,基本上對tornado的異步設置有了概覽,不過,上面的程序在實際中沒有什么價值。在工程中,要讓tornado網站真正異步起來,還要做很多事情,不僅僅是如上面的設置,因為很多東西,其實都不是異步的。 ## [](https://github.com/qiwsir/StarterLearningPython/blob/master/309.md#實踐中的異步)實踐中的異步 以下各項同步(阻塞)的,如果在tornado中按照之前的方式只用它們,就是把tornado的非阻塞、異步優勢削減了。 * 數據庫的所有操作,不管你的數據是SQL還是noSQL,connect、insert、update等 * 文件操作,打開,讀取,寫入等 * time.sleep,在前面舉例中已經看到了 * smtplib,發郵件的操作 * 一些網絡操作,比如tornado的httpclient以及pycurl等 除了以上,或許在編程實踐中還會遇到其他的同步、阻塞實踐。僅僅就上面幾項,就是編程實踐中經常會遇到的,怎么解決? 聰明的大牛程序員幫我們做了擴展模塊,專門用來實現異步/非阻塞的。 * 在數據庫方面,由于種類繁多,不能一一說明,比如mysql,可以使用[adb](https://github.com/ovidiucp/pymysql-benchmarks)模塊來實現python的異步mysql庫;對于mongodb數據庫,有一個非常優秀的模塊,專門用于在tornado和mongodb上實現異步操作,它就是motor。特別貼出它的logo,我喜歡。官方網站:[http://motor.readthedocs.org/en/stable/](http://motor.readthedocs.org/en/stable/)上的安裝和使用方法都很詳細。 [![](https://box.kancloud.cn/2015-09-07_55ed5e466465d.png)](https://github.com/qiwsir/StarterLearningPython/blob/master/3images/30901.png) * 文件操作方面也沒有替代模塊,只能盡量控制好IO,或者使用內存型(Redis)及文檔型(MongoDB)數據庫。 * time.sleep()在tornado中有替代:`tornado.gen.sleep()`或者`tornado.ioloop.IOLoop.instance().add_timeout`,這在前面代碼已經顯示了。 * smtp發送郵件,推薦改為tornado-smtp-client。 * 對于網絡操作,要使用tornado.httpclient.AsyncHTTPClient。 其它的解決方法,只能看到問題具體說了,甚至沒有很好的解決方法。不過,這里有一個列表,列出了足夠多的庫,供使用者選擇:[Async Client Libraries built on tornado.ioloop](https://github.com/tornadoweb/tornado/wiki/Links),同時這個頁面里面還有很多別的鏈接,都是很好的資源,建議讀者多看看。 教程到這里,讀者是不是要思考一個問題,既然對于mongodb有專門的motor庫來實現異步,前面對于tornado的異步,不管是哪個裝飾器,都感覺麻煩,有沒有專門的庫來實現這種異步呢?這不是異想天開,還真有。也應該有,因為這才體現python的特點。比如[greenlet-tornado](https://github.com/mopub/greenlet-tornado),就是一個不錯的庫。讀者可以瀏覽官方網站深入了解(為什么對mysql那么不積極呢?按理說應該出來好多支持mysql異步的庫才對)。 必須聲明,前面演示如何在tornado中設置異步的代碼,僅僅是演示理解設置方法。在工程實踐中,那個代碼的意義不到。為此,應該有一個近似于實踐的代碼示例。是的,的確應該有。當我正要寫這樣的代碼時候,在網上發現一篇文章,這篇文章阻止了我寫,因為我要寫的那篇文章的作者早就寫好了,而且我認為表述非常到位,示例也詳細。所以,我不得不放棄,轉而推薦給讀者這篇好文章: 舉例:[http://emptysqua.re/blog/refactoring-tornado-coroutines/](http://emptysqua.re/blog/refactoring-tornado-coroutines/)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看