Python web框架 Tornado(二)異步非阻塞


異步非阻塞

阻塞式:(適用於所有框架,Django,Flask,Tornado,Bottle)
  一個請求到來未處理完成,后續一直等待
  解決方案:多線程,多進程

異步非阻塞(存在IO請求): Tornado(單進程+單線程)
  使用異步非阻塞,需要遵循Tornado框架內部規則,gen

  多個連接請求,連接給服務端,如果是有異步非阻塞的話,服務端會接收所有的請求交由后台處理,等待其他鏈接的同時,原先連接不斷開,直至返回后台處理完成的結果!
  外部請求,連接服務端 或在select中創建Future對象,然后服務端再把請求交給業務處理平台,此時select監聽的列表中又會生成一個socket對象,當業務平台對請求處理完成之后就會把信息返回到服務端的select監聽列表中,同時對這個Future對象賦值,用於標記服務端是否要給客戶端返回請求信息。

  執行流程,本質上都是返回一個future對象,如果對這個對象被set_result了就返回值,否則就是夯住,一直保持連接,不終止請求。

1、基本使用

裝飾器 + Future 從而實現Tornado的異步非阻塞

class AsyncHandler(tornado.web.RequestHandler):
 
    @gen.coroutine
    def get(self):
        future = Future()
        future.add_done_callback(self.doing)
        yield future
        # 或
        # tornado.ioloop.IOLoop.current().add_future(future,self.doing)
        # yield future
 
    def doing(self,*args, **kwargs):
        self.write('async')
        self.finish()

  當發送GET請求時,由於方法被@gen.coroutine裝飾且yield 一個 Future對象,那么Tornado會等待,等待用戶向future對象中放置數據或者發送信號,如果獲取到數據或信號之后,就開始執行doing方法。

  異步非阻塞體現在當在Tornaod等待用戶向future對象中放置數據時,還可以處理其他請求。

  注意:在等待用戶向future對象中放置數據或信號時,此連接是不斷開的。

2、同步阻塞和異步非阻塞對比

class SyncHandler(tornado.web.RequestHandler):

    def get(self):
        self.doing()
        self.write('sync')

    def doing(self):
        time.sleep(10)
同步阻塞
class AsyncHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        future = Future()
        tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.doing)
        yield future


    def doing(self, *args, **kwargs):
        self.write('async')
        self.finish()
異步非阻塞

3、httpclient類庫

  Tornado提供了httpclient類庫用於發送Http請求,其配合Tornado的異步非阻塞使用。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
 
import tornado.web
from tornado import gen
from tornado import httpclient
 
# 方式一:
class AsyncHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self, *args, **kwargs):
        print('進入')
        http = httpclient.AsyncHTTPClient()
        data = yield http.fetch("http://www.google.com")
        print('完事',data)
        self.finish('6666')
 
# 方式二:
# class AsyncHandler(tornado.web.RequestHandler):
#     @gen.coroutine
#     def get(self):
#         print('進入')
#         http = httpclient.AsyncHTTPClient()
#         yield http.fetch("http://www.google.com", self.done)
#
#     def done(self, response):
#         print('完事')
#         self.finish('666')
 
 
 
application = tornado.web.Application([
    (r"/async", AsyncHandler),
])
 
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start() 
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
需要先安裝支持異步操作Mysql的類庫: 
    Tornado-MySQL: https://github.com/PyMySQL/Tornado-MySQL#installation
    
    pip3 install Tornado-MySQL

"""

import tornado.web
from tornado import gen

import tornado_mysql
from tornado_mysql import pools

POOL = pools.Pool(
    dict(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb'),
    max_idle_connections=1,
    max_recycle_sec=3)


@gen.coroutine
def get_user_by_conn_pool(user):
    cur = yield POOL.execute("SELECT SLEEP(%s)", (user,))
    row = cur.fetchone()
    raise gen.Return(row)


@gen.coroutine
def get_user(user):
    conn = yield tornado_mysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb',
                                       charset='utf8')
    cur = conn.cursor()
    # yield cur.execute("SELECT name,email FROM web_models_userprofile where name=%s", (user,))
    yield cur.execute("select sleep(10)")
    row = cur.fetchone()
    cur.close()
    conn.close()
    raise gen.Return(row)


class LoginHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.render('login.html')

    @gen.coroutine
    def post(self, *args, **kwargs):
        user = self.get_argument('user')
        data = yield gen.Task(get_user, user)
        if data:
            print(data)
            self.redirect('http://www.oldboyedu.com')
        else:
            self.render('login.html')


application = tornado.web.Application([
    (r"/login", LoginHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
基於異步非阻塞和Tornado-MySQL實現用戶登錄示例

異步非阻塞漸進學習代碼+筆記注釋

import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado.httpserver import HTTPServer

# 單線程操作,請求來排隊等待,順序執行
#人為干預模擬IO設置 sleep10秒
class IndexHandler(RequestHandler):
    def get(self):
        print('開始')
        import time
        time.sleep(10)
        self.write("Hello, world")
        print('結束')

application = tornado.web.Application([
    (r"/index", IndexHandler),
])


if __name__ == "__main__":
    # 單進程單線程
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

    # 利用多進程 實現
    # server = HTTPServer(application)
    # server.bind(8888)
    # server.start(4)  # Forks multiple sub-processes
    # tornado.ioloop.IOLoop.current().start()
s1.py
import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado import gen # 執行異步IO 導入gen模塊
from tornado.concurrent import Future # 執行異步IO導入 Future模塊,引用Future對象
import time

# 單線程 實現異步非阻塞操作!所有的連接請求不等待直接執行
class IndexHandler(RequestHandler):
    @gen.coroutine # 異步IO 固定寫法,在請求上以裝飾器的形式添加
    def get(self):
        print('開始')
        future = Future() #創建 Future() 對象
        tornado.ioloop.IOLoop.current().add_timeout(time.time() + 10, self.doing) #給當前的客戶端添加超時時間,固定寫法
        yield future # yield 返回 Future() 對象 IO操作的固定寫法

    # 操作完成之后,需要執行的回調函數,一般是用於給請求返回消息
    def doing(self, *args, **kwargs):
        self.write('async') # 返回消息
        self.finish() #結束連接


application = tornado.web.Application([
    (r"/index", IndexHandler),
])


if __name__ == "__main__":
    # 單線程
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

    # 多進程
    # server = HTTPServer(application)
    # server.bind(8888)
    # server.start(4)  # Forks multiple sub-processes
    # tornado.ioloop.IOLoop.current().start()
s2.py
import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado import gen
from tornado.concurrent import Future
import time
from tornado import httpclient #針對HTTP請求進行異步非阻塞處理的模塊

# 針對API接口 HTTP請求 實現異步非阻塞
class IndexHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        print('收到訂單')
        http = httpclient.AsyncHTTPClient() #創建 執行異步非阻塞 客戶端
        yield http.fetch("http://www.github.com", self.done) # 固定寫法 請求對某個API接口(url地址)傳遞消息,處理完畢執行回調函數

    #請求處理完畢,執行的回調函數。
    def done(self, response):
        self.write('訂單成功') # 給請求返回的信息
        self.finish() # 斷開連接

application = tornado.web.Application([
    (r"/index", IndexHandler),
])


if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

    # server = HTTPServer(application)
    # server.bind(8888)
    # server.start(4)  # Forks multiple sub-processes
    # tornado.ioloop.IOLoop.current().start()
s3.py
import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado import gen
from tornado.concurrent import Future
import time
from tornado import httpclient

fu = None
class IndexHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        global fu
        print('瘋狂的追求')
        fu = Future() # 創建Future對象,建立連接,如果沒有人改變狀態,請求就會永久存在連接不斷開,除非Future() 對象被賦值或是反生改變
        # fu.set_result("") # 給Future 對象賦值,fu發生變化,返回請求。
        fu.add_done_callback(self.done)# 給fu添加要執行的回調函數
        yield fu

    def done(self, response):
        self.write('終於等到你')
        self.finish()

class TestHandler(RequestHandler):
    def get(self):
        fu.set_result(666) # 給future對象賦值,用以改變連接狀態,返回消息 (注意:返回的內容就是 result的值)
        self.write('我只能幫你到這里了')

application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/test", TestHandler),
])


if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
s4.py
import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado import gen
from tornado.concurrent import Future
import time
from tornado import httpclient
from threading import Thread

def waiting(futher):
    #線程要執行處理的函數
    import time
    time.sleep(10)
    futher.set_result(666)


class IndexHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        global fu
        print('瘋狂的追求')
        fu = Future()
        fu.add_done_callback(self.done)

        thread = Thread(target=waiting,args=(fu,)) # 開一個線程 自動給設置值,以自動給請求返回處理的消息
        thread.start()

        yield fu

    def done(self, response):
        self.write('終於等到你')
        self.finish()


application = tornado.web.Application([
    (r"/index", IndexHandler),
])


if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
s5.py
import tornado.web
import tornado.ioloop
from tornado import gen
import tornado_mysql

@gen.coroutine # 注意需要寫上裝飾器
def get_user(user):
    # 異步非阻塞,Task操作的函數,連接數據庫,注意語法結構
    conn = yield tornado_mysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='tornadoTest',
                                       charset='utf8')
    cur = conn.cursor()
    # yield cur.execute("SELECT name,email FROM web_models_userprofile where name=%s", (user,))
    yield cur.execute("select sleep(10)")
    row = cur.fetchone()
    cur.close()
    conn.close()
    raise gen.Return(row) # 注意task函數的返回值


class LoginHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.render('login.html')

    @gen.coroutine
    def post(self, *args, **kwargs):
        user = self.get_argument('user')
        data = yield gen.Task(get_user, user) # 執行Task函數,內部還是返回future對象。Task函數上第一個參數是要執行的函數,第二個是參數
        if data:
            print(data)
            self.redirect('http://www.baidu.com')
        else:
            self.render('login.html')

    #原始方案,請求來了,連接數據庫,等待操作完成,關閉連接!
    # def post(self, *args, **kwargs):
    #     user = self.get_argument('user')
    #     # 連接數據庫: IO耗時
    #     # 查詢語句: IO耗時
    #     # 獲取結果
    #     data = {'id':1,'user':'alex'}
    #     if data:
    #         print(data)
    #         self.redirect('http://www.baidu.com')
    #     else:
    #         self.render('login.html')


application = tornado.web.Application([
    (r"/login", LoginHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
s6.py

 web框架之Tornado


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM