理解GEVENT的协程调度 – 如何在GEVENT里结束一个CPU BOUND 操作

说起这个需求,其实是来自于实现一个Gevent内的代码执行器的逻辑。

这个逻辑要求定时运行某个用户的callback,但是必须不能一直执行这个callback,所以要有Timeout,在Timeout到达的时候,自动kill掉用户的Greenlet.

Gevent自带一个Timeout的实现,同时Greenlet也都表现为DaemonThread运行。

但是经过尝试发现,Gevent的Timeout在这种场景下无法工作。

import gevent
import time
 
 
def fucking_loop():
    while True:
        pass
 
 
def fucking_loop_with_timeout():
    with gevent.Timeout(10):
        while True:
            pass
 
 
def fail_to_kill():
    deadline = time.time() + 10
    
    thread = gevent.spawn(fucking_loop)
    
    while deadline > time.time():
        # gevent.sleep will switch out of main thread
        gevent.sleep(1)
    
    # This line of code will never been executed
    # because gevent will not switch to main thread.
    thread.kill()
 
 
def fail_to_kill_2():
    deadline = time.time() + 10
 
    thread = gevent.spawn(fucking_loop)
    
    # Will never timeout because of the same reason 
    fucking_loop_with_timeout()
 
    # This line of code will never been executed
    # because gevent will not switch to main thread.
    thread.kill()

Gevent的Greenlet切换都是发生在等待IO的时候,并不会在需要所有CPU的地方发生切换,因此,对于空的While Loop,Gevent主线程或者说切换器,永远没有机会执行,因为空的while loop会一直吃CPU,而不会触发switch,这样Timeout的执行也就无从谈起了。

关于Gevent切换的讨论,可以参见:

https://groups.google.com/forum/#!topic/gevent/eBj9YQYGBbc

http://stackoverflow.com/questions/23513472/how-to-stop-a-gevent-micro-thread-when-it-in-a-while-loop

Gevent无法处理这种业务代码的情况下,有没有别的思路或者工具可以处理Timeout这种场景呢?

答案是操作系统的Signal API。

https://docs.python.org/2/library/signal.html

操作系统的SignalAPI会将Callback注册到操作系统的Timer里,这个Timer不受用户态调度器Gevent(Greenlet)的影响,因此不论Gevent的实现逻辑是什么样子的,操作系统都会在时间条件符合设置的时候,调用我们之前注册的Callback,这样一来,我们想要的Timeout功能就可以合理实现了。

最终可以工作的 版本:

import gevent
import time
 
 
def fucking_loop():
    while True:
        pass
 
 
def kill_fucking_loop_works():
    import signal
    timeout = 10
 
    thread = gevent.spawn(fucking_loop)
 
    def kill_thread(*args, **kwargs):
        if not thread.dead:
            thread.kill(timeout=1)
 
    signal.signal(signal.SIGALRM, kill_thread)
    signal.alarm(int(timeout))
Last modification:August 14th, 2017 at 10:58 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment