Gevent: be or not to be?
Александр Мокров
About
● asynchrony
● gevent
● asyncio
● пример построения асинхронного
приложения
Web APP
Task queue
Task queue
Task queue
WorkerWorkerWorker
DB
Push tasks
into queue
Workers pull task
from queue
Report progress
Pull DP for
report status
Tasks workflow
get flour
bake pie
get meat
seal pie
create
dough
order pie
get milk
get aggs
Applications as services
Service 1
Worker
Worker
Service 3
Worker
Worker
Service 2
Worker
Service 2
Worker
Worker
Worker
Worker
Long polling
client
request
response
request
response
request
response
request
response
server
event
event
event
event
Many same services
entry
point
service
n
app
service
2
service
1
...
entry
task
service
task 1
DB
callback
task 1
service
task n
callback
task n
Callbacks
entry
task
service
task 1
DB
callback
task 1
service
task n
callback
task n
Callbacks
entry task
service
task 1
DB
callback
task 1
service
task n
callback
task n
Callbacks
What we want
entry task
service task
1
service task
2
service task
n
What we want
entry task
service task
1
service task
2
service task
n
long task services
time
Timeline
long task services
time
Timeline
long task services
time
Timeline
long task services
time
Timeline
timeout
Gevent
gevent is a concurrency library based around libev. It
provides a clean API for a variety of concurrency and network
related tasks.
Greenlet
The primary pattern used in gevent is the Greenlet, a lightweight coroutine
provided to Python as a C extension module. Greenlets all run inside of
the OS process for the main program but are scheduled cooperatively.
Only one greenlet is ever running at any given time.
Spin-off of Stackless, a version of CPython that supports micro-threads
called “tasklets”. Tasklets run pseudo-concurrently (typically in a single or
a few OS-level threads) and are synchronized with data exchanges on
“channels”.
Its coroutine
The way to gevent
● Stackless
● Greenlet
● Eventlet
● Gevent
Event loop
def foo():
print('Running in foo')
gevent.sleep(0)
print('Explicit context switch to foo')
def bar():
print('Explicit context to bar')
gevent.sleep()
print('Implicit context switch to bar')
gevent.joinall([
gevent.spawn(foo),
gevent.spawn(bar),
])
Synchronous & Asynchronous Execution. Context Switching
Running in foo
Explicit context to bar
Explicit context switch to foo
Implicit context switch to bar
def task(pid):
gevent.sleep(random.randint(0,2)*0.001)
print('Task %s done' % pid)
def synchronous():
for i in range(1, 8):
task(i)
Synchronous & Asynchronous Execution
Task 1 done
Task 2 done
Task 3 done
Task 4 done
Task 5 done
Task 6 done
Task 7 done
def task(pid):
gevent.sleep(random.randint(0,2)*0.001)
print('Task %s done' % pid)
def asynchronous():
threads = [gevent.spawn(task, i)
for i in range(1, 8)]
gevent.joinall(threads) Task 1 done
Task 5 done
Task 6 done
Task 2 done
Task 4 done
Task 7 done
Task 3 done
Synchronous & Asynchronous Execution
Blocking calls
Blocking
operation
Non-blocking
asynchronous call
Callbacks?
def callback_hell(param):
def _cb1(result):
def _cb2(result):
def _cb3(result):
def _cb4(result):
def _cb5(result):
def _cb6(result):
def _cb7(result):
def _cb8(result):
return func9(param).add_callback(_cb2)
return func8(param).add_callback(_cb2)
return func7(param).add_callback(_cb2)
return func6(param).add_callback(_cb2)
return func5(param).add_callback(_cb2)
return func4(param).add_callback(_cb2)
return func3(param).add_callback(_cb2)
return func2(param).add_callback(_cb2)
return func1(param).add_callback(_cb1)
Callback hell
def echo(i):
time.sleep(0.001)
return i
# Non Deterministic Process Pool
from multiprocessing.pool import Pool
p = Pool(10)
run1 = [a for a in p.imap_unordered(echo, range(10))]
run2 = [a for a in p.imap_unordered(echo, range(10))]
run3 = [a for a in p.imap_unordered(echo, range(10))]
run4 = [a for a in p.imap_unordered(echo, range(10))]
print(run1 == run2 == run3 == run4)
False
Determinism
# Deterministic Gevent Pool
from gevent.pool import Pool
p = Pool(10)
run1 = [a for a in p.imap_unordered(echo, range(10))]
run2 = [a for a in p.imap_unordered(echo, range(10))]
run3 = [a for a in p.imap_unordered(echo, range(10))]
run4 = [a for a in p.imap_unordered(echo, range(10))]
print(run1 == run2 == run3 == run4)
True
Determinism
Spawning Greenlets
from gevent import Greenlet
thread1 = Greenlet.spawn(foo, "message", 1)
thread2 = gevent.spawn(foo, "message", 2)
thread3 = gevent.spawn(lambda x: (x+1), 2)
threads = [thread1, thread2, thread3]
# Block until all threads complete.
gevent.joinall(threads)
class MyGreenlet(Greenlet):
def __init__(self, message, n):
Greenlet.__init__(self)
self.message = message
self.n = n
def _run(self):
print(self.message)
gevent.sleep(self.n)
g = MyGreenlet("Hi there!", 3)
g.start()
g.join()
Spawning Greenlets
Greenlet State
started -- Boolean, indicates whether the Greenlet has been
started
ready() -- Boolean, indicates whether the Greenlet has halted
successful() -- Boolean, indicates whether the Greenlet has
halted and not thrown an exception
value -- arbitrary, the value returned by the Greenlet
exception -- exception, uncaught exception instance thrown
inside the greenlet
greenlet
greenlet
long task
service response
listener
subscribe(task_id)
Greenlets subscribing
from gevent import Timeout
timeout = Timeout(seconds=10)
timeout.start()
def wait():
gevent.sleep(10)
print('Completed')
try:
gevent.spawn(wait).join()
except Timeout:
print('Could not complete')
Timeouts
time_to_wait = 5 # seconds
class TooLong(Exception):
pass
with Timeout(time_to_wait, TooLong):
gevent.sleep(10)
Timeouts
class Queue(maxsize=None, items=None)
empty()
full()
get(block=True, timeout=None)
get_nowait()
next()
peek(block=True, timeout=None)
peek_nowait()
put(item, block=True, timeout=None)
put_nowait(item)
qsize()
Queues
greenlet
greenlet
greenlet
service
results
dispatcher
gevent.queues
task_id
reply_to, results_queue
Getting results
greenlet
greenlet
greenlet
service
results
dispatcher
service service service
reply_to
services queues
subscribe
gevent.queues
Asynchronous RPC
Events
Groups and
Pools
Locks and
Semaphores
Subprocess
Thread
Locals
Actors
Monkey patching
guerrilla
patch
gorilla
patch
monkey
patch
import socket
print(socket.socket)
from gevent import monkey
monkey.patch_socket()
print("After monkey patch")
print(socket.socket)
import select
print(select.select)
monkey.patch_select()
print("After monkey patch")
print(select.select)
<class 'socket.socket'>
After monkey patch
<class
'gevent._socket3.socket'>
<built-in function select>
After monkey patch
<function select at
0x7ff7e111c378>
Monkey patching
Stack layout for a greenlet
| ^^^ |
| older data |
| |
stack_stop . |_______________|
. | |
. | greenlet data |
. | in stack |
. * |_______________| . . _____________ stack_copy +
stack_saved
. | | | |
. | data | |greenlet data|
. | unrelated | | saved |
. | to | | in heap |
stack_start . | this | . . |_____________| stack_copy
| greenlet |
| |
| newer data |
| vvv |
What instead?
PEP 255 -- Simple Generators
Authors:
Neil Schemenauer <nas at arctrix.com>,
Tim Peters <tim.peters at gmail.com>,
Magnus Lie Hetland <magnus at hetland.org>
Created: 18-May-2001
Python 2.2
PEP 342 Coroutine via Enhanced Generators
Authors: Guido van Rossum, Phillip J. Eby
Created: 10-May-2005
Python 2.5
PEP 288, Generators Attributes and Exceptions
(Raymond Hettinger)
PEP 325, Resource-Release Support for Generators
(Samuele Pedroni)
What’s new?
1. (yield) statement:
value = (yield)
2. send()
3. throw()
4. close()
5. Ensure that close() is called when a generator iterator is
garbage-collected.
6. Allow yield to be used in try/finally blocks
Cooperative
Coroutine Coroutine
Coroutine
Coroutine
Coroutine
Produce, Filter, and Consume
producer consumerfilterfilter ...
send sendsend
(yield) (yield)(yield)
RESULT = yield from EXPR
PEP 380 Syntax for Delegating to aSubgenerator
Authors: Gregory Ewing
Created: 13-Feb-2009
Python 3.3 - September 29, 2012
PEP 0492 -- Coroutines with async and await syntax
Authors: Yury Selivanov
Created: 09-Apr-2015
Python 3.5
asynciotulip
Event loop
async def coroutine():
print('Running in coro')
event_loop = asyncio.get_event_loop()
try:
print('init coroutine')
coro = coroutine()
print('waiting for ...')
event_loop.run_until_complete(coro)
finally:
print('closing event loop')
event_loop.close()
init coroutine
waiting for ...
Running in coro
closing event loop
Coroutines, futures and tasks
● Coroutines
○ async def
○ @asyncio.coroutine
● Futures
○ promise of a result or an error
● Tasks
○ future which runs a coroutine
Chaining coroutines
async def chain():
result1 = await step1()
result2 = await step2(result1)
result3 = await step3(result2)
return result2, result3
async def task(pid):
asyncio.sleep(random.randint(0, 2)*0.001)
print(f'Task {pid} done')
loop = asyncio.get_event_loop()
futures = asyncio.gather(
*[task(i) for i in range(7)])
loop.run_until_complete(futures)
loop.close()
Task 3 done
Task 2 done
Task 4 done
Task 5 done
Task 1 done
Task 6 done
Task 0 done
Asynchronous Execution
Queues
● Queue
● PriorityQueue
● LifoQueue
Transport and protocols
(callback based API)
Streams
(coroutine based API)
greenlet
greenlet
coro
service
results
dispatcher
service service service
reply_to
services queues
subscribe
asyncio.queues
Asynchronous RPC
server
dispatcher
Application structure
tasks
tasksservice
tasks
client
client
Client
Client/
Server
Server
Client/
Server
Client/
Server
Server
Server
Constructor
Combining Coroutines with Threads and Processes
executor =
concurrent.futures.ProcessPoolExecutor(
max_workers=3,
)
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(
run_blocking_tasks(executor)
)
finally:
event_loop.close()
Summary
● В мире python много библиотек хороших и разных
● Некоторые библиотеки, когда-то популярные,
уступают свое место новым, более продуманным
● Чтобы крепче спать, не ковыряйтесь в потрохах
● Не всё то, чем кажется
References
http://www.gevent.org/
http://sdiehl.github.io/gevent-tutorial/
https://greenlet.readthedocs.io/en/latest/
https://docs.python.org/3/library/asyncio.html
https://github.com/aio-libs
Thank you!
Questions?
alexander.mokrov@gmail.com

Gevent be or not to be