![](/img/trans.png)
[英]gevent to Tornado ioloop - Structure code with coroutines/generators
[英]Calling Tornado coroutines from synchronous code
這將是一個很長的問題,所以:
TL; DR:我有一個帶有請求處理程序的Python 2.7線程網絡服務器,調用堆棧如下所示:
WorkerThread -> requestHandler -> func1 -> func2 -> .. -> func10 -> doStuff -> BlockingIO
我想使用Tornado 3.0 IOLoop並僅更改服務器和IO部分:
(IOLoop) -> requestHandler -> func1 -> func2 -> .. -> func10 -> (doStuff) -> (AsyncIO)
所以requestHandler()和func10()之間的所有代碼堆都不會改變。 事實上,即使doStuff()的界面也不會改變,它似乎會被阻止。 但是,在內部它將使用AsyncIO對象(作為Tornado協程),並在異步IO操作期間產生IOLoop以執行其他協同程序,直到IO操作完成。
這可能嗎?
現在來一個幾乎真實的例子:
我有一個網絡服務器接收請求並使用線程池(或進程池,處理它們,就此示例而言無關緊要):
def main():
# Main entry point, called below.
# Fake class, you can imagine the internals. We register a request
# handler here - handleRequest()
server = ThreadedServer(handler=handleRequest)
# Server has a thread pool, each request is handled on a worker thread.
# One thread handles network stuff and pushes requests to worker threads
Server.start()
def handleRequest(server_address):
# This is the request handler, called in the context of a worker
# thread, after a network request was received.
# We call the function below. It blocks the thread until it finishes.
# Not very optimal, since the blocking is network IO bound
result = doStuff(server_address)
# We use the result somehow, here we print it
print "Request handled with result: %s" % result
def doStuff(server_address):
# This is called by the request handler
# This is a network bound object, most of its time is spent waiting
# for the network IO
net_bound_object = NetBoundSyncObject(server_address)
# This would block, waiting on the network, preventing the thread from
# handling other requests
result = net_bound_object.do_something()
# We have the result, return it
return result
if __name__ == "__main__":
main()
很簡單,真的。
現在,假設我已經決定要重構我的服務器以使用Tornado,使用tornado.gen來支持異步操作,因此不會受到網絡IO的限制。 所以,這是我的新代碼:
def main():
# Start Tornado's IOLoop, first entering TornadoServer.start() to begin
# initializing the server and accept requests.
# server.start is a coroutine that waits for network IO, yielding
# control back to the IOLoop until something
# happens. When something does, it is awakened and schedules a
# request handler - handleRequest, and goes back to network IO,
# yielding control. Thus, handleRequest is called.
server = TornadoServer(handler=handleRequest) # fake class again
IOLoop.instance().add_callback(server.start)
IOLoop.instance().start()
def handleRequest(server_address):
# This part of the code has not been changed - just the comments.
# It is now run in the context of an IOLoop callback.
# We call the function above. The interface remains the same. It also seems
# to block - which is fine, we want to wait for its result to continue processing.
# However, we want the IOLoop to continue running somehow.
result = doStuff(server_address)
# We use the result somehow, here we print it
print "Request handled with result: %s" % result
def doStuff(server_address):
# This is a network bound object, most of its time is spent waiting for
# the network IO, however all its methods are coroutines and it yields
# while waiting for network IO
net_bound_object = NetBoundAsyncObject(server_address)
# Now to the problem.
# doStuff() is a facade - I don't want it to be a coroutine, I want it to hide
# the implementation details and keep its previous interface.
# However, NetBoundAsyncObject.do_something_async() is a coroutine, and calls
# coroutines inside it. So it should be called in the context of
# another coroutine:
result = yield net_bound_object.do_something_async()
# but this is wrong here, since we are not a coroutine.
# To properly call it asynchronously, I would need to make doStuff()
# a coroutine as well, breaking its interface, which would mean that
# handleRequest too should now be a coroutine. Not a big change, but imagine
# that instead of calling doStuff() directly, I had code like:
# handleRequest -> func1 -> func2 -> func3 -> ... -> func10 -> doStuff
# so now I'd have to change all these functions to be coroutines as well.
# All of these functions, handleRequest and func1..10, represent a big stack
# of code in my real system which is completely synchronous, CPU bound code,
# so it has no IO waits anywhere, just code that needs to be run BEFORE and
# AFTER the network IO bound code finishes, to properly handle the request.
# It is well tested, production proven code that requires no functional change,
# and that doesn't need to be a coroutine. This would be a big refactor.
# In the code as it is now, result is now returned as a Future:
result = net_bound_object.do_something_async()
# I want to be able to do something like:
IOLoop.instance().wait_for_future(result)
# Letting the IOLoop run and handle other things in the meanwhile, like
# network requests, and also my asynchronous code.
# When it finishes, I want my wait_for_future() to return and to continue
# execution with the result accessible in the future object.
# Thus, the changes would be at the top (the TornadoServer vs ThreadedServer)
# and the bottom (doStuff to use either NetBoundObject or NetBoundAsyncObject),
# but the middle stack will remain unchanged.
# Return the result of the operation
return result
if __name__ == "__main__":
main()
我知道這在許多方面都存在問題,主要是因為調用堆棧。 當我們做類似的事情:
IOLoop.instance().wait_for_future(result)
我們有一個看起來像這樣的調用堆棧:
IOLoop.main_loop.start() -> handleRequest -> IOLoop.main_loop.wait_for_future() -> other_callbacks..
所以我們可能(甚至可能)遇到這樣的情況:
IOLoop.main_loop.start() -> handleRequest -> IOLoop.main_loop.wait_for_future() -> handleRequest -> IOLoop.main_loop.wait_for_future() -> handleRequest -> IOLoop.main_loop.wait_for_future() -> ...
很明顯,如果handleRequest本身成為一個協程,那么當它本身產生時,我們就沒有這么深的堆棧問題。
在我曾經使用的嵌入式系統中,使用非搶先式調度程序,在任何時候都沒有問題將控制權返回給調度程序而沒有堆棧問題。 調度程序將獲取執行上下文並調用堆棧並存儲它們,並更改為另一個上下文/堆棧並從那里繼續執行。 在等待事件/ IO時,將觸發調度程序並運行IO循環中的任何內容。 我想在我的系統中使用這樣的東西,而不是必須更改上面的整個調用堆棧 - 將所有內容轉換為協同程序。
有沒有任何提示,任何想法?
您可以使用以下命令同步運行@ gen.coroutine修飾函數:
@gen.coroutine
def main():
# do stuff...
if __name__ == '__main__':
IOLoop.instance().run_sync(main)
這將啟動'IOLoop',運行該函數,並停止循環。 https://github.com/facebook/tornado/blob/master/tornado/ioloop.py
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.