異步回調程式的原理和寫法我不就不介紹了,因為我主要是來吐槽下這種代碼風格的。
最近因為追求性能是以去看了下tornado,然後發現這貨如果你要利用它的高性能 ,那麼你就要寫回調代碼,跟twisted一樣,各種callback。
我們正常的同步代碼一般是這樣的
res = db.query(...)
res2 = dosomething(res)
res3 = db.insert(...)
return res3
上面的代碼共四行,2次資料庫操作,一次資料處理最後傳回想要的結果。邏輯很清晰也很易懂,我想大部分的程式都是這樣的。但如如果是異步的寫法就會這麼寫
db.query(...,callback=fun2)
def fun2(res):
res2 = dosomething(res,callback=fun3)
def fun3(res):
db.insert(...,callback=fun4)
異步的代碼就是隻要你涉及到阻塞的代碼就需要使用回調,呵呵,看到了吧2次阻塞就要求你把一段代碼寫成3段,代碼易讀性直線下降,如果你隻是做一些簡單的功能,代碼也不多,我覺得還是能接受的,畢竟性能真的好,但是如果你要做一個很大的項目,一個請求可能要做4,5次阻塞操作,不敢想象,簡直就是個災難。
不過好在tornado還給了我們另一條活路,那就是tornado.gen http://www.tornadoweb.org/documentation/gen.html。
簡單點說就是gen 給了我們用同步代碼來做異步實作的可能。
我們來看看gen和普通異步代碼。
class BaseHandler(tornado.web.RequestHandler):
@property
def db(self):
if not hasattr(self, "_db"):
self._db = asyncmongo.Client(pool_id='test_pool', **settings.get('mongo_database'))
return self._db
def api_response(self, data):
"""将資料轉成json傳回給用戶端"""
self.set_header("Content-Type", "application/json; charset=UTF-8")
data = dumps(data)
self.finish(data)
class A(BaseHandler):
"""
同步代碼實作異步
"""
@tornado.web.asynchronous
@gen.engine
def get(self):
res = self.get_user()
@tornado.web.asynchronous
@gen.engine
def get_user(self):
user_id = 'asdf'+str(random.randrange(1,490000)) #随機産生一個資料庫裡有的使用者
res = yield gen.Task(self.db.user.find,{'name':user_id},limit=1)
res=res[0][0][0]
self.api_response(res)
class B(BaseHandler):
"""
異步代碼
"""
@tornado.web.asynchronous
def get(self):
user_id = 'asdf'+str(random.randrange(1,490000))#随機産生一個資料庫裡有的使用者
self.db.user.find({ 'name': user_id }, callback=self.async_callback(self.finish_save))
def finish_save(self, response, error):
self.api_response(response[0])
最後的最後我分别測試了2中寫法最後在性能上的結果,我用ab -n 10000 -c 200 做了測試
gen 的代碼 用了7秒
普通的異步代碼 用了6.3秒
我覺得gen 略慢吧,但還是在可接受範圍内,畢竟我不用寫你妹的回調了。。。。怨念
當然 如果大家不是寫web 就直接用gevent 吧,連這些gen的裝飾器都可以省了。