monkey patch

                                                                          原图出处

Overview

Context Switch 是另一个和并发相随的问题,和线程不同,协程的上下文切换完全由应用程序负责,操作系统并不感知。以 nova-api 为例,在操作系统看来,该进程只有一个线程,既主线程,虽然主线程里面运行着多个协程,当其中一个协程阻塞(如 IO 阻塞)时,如果没有应用程序调度切换该协程,就会阻塞该线程里的所有其它协程,影响并发效率。所以应用程序必须能够感知切换的时机,对于阻塞的场景,需要一种机制来触发切换该协程,运行其它的协程。

Eventlet Monkey Patch 动态地修改某些标准库,使得库里的阻塞函数被调用时触发切换,把 CPU 片段留给其它的协程。相关函数如下:

  • eventlet.patcher.monkey_patch(os=None, select=None, socket=None, thread=None, time=None, psycopg=None):它可以修改如下 8 种 module:
    • os
    • select
    • socket
    • thread
    • time
    • psycopg
    • MySQLdb
    • builtins
  • eventlet.patcher.is_monkey_patched(module):检查某个 module 是否已被 monkey patch

Eventlet 遇到如下场景时,会触发上下文切换:

  • eventlet.sleep()
  • 上述被 monkey patch 的 module 的某些函数

Monkey Patch in Nova

Eventlet Best Practices 介绍了 monkey patch 的一些使用准则。

  • Do it first or not at all:此话之意就是如果要使用 monkey patch,那么应该在 python 程序启动时使用,切勿在中途使用,避免引起问题。以 nova 为例,nova/cmd/__init__.py 和 nova/tests/unit/__init__.py 处使用了 monkey patch,而这两个文件,恰恰是程序的最初入口
# TODO(mikal): move eventlet imports to nova.__init__ once we move to PBR
import os
import sys

# NOTE(mikal): All of this is because if dnspython is present in your
# environment then eventlet monkeypatches socket.getaddrinfo() with an
# implementation which doesn't work for IPv6. What we're checking here is
# that the magic environment variable was set when the import happened.
if ('eventlet' in sys.modules and
        os.environ.get('EVENTLET_NO_GREENDNS', '').lower() != 'yes'):
    raise ImportError('eventlet imported before nova/cmd/__init__ '
                      '(env var set to %s)'
                      % os.environ.get('EVENTLET_NO_GREENDNS'))

os.environ['EVENTLET_NO_GREENDNS'] = 'yes'

import eventlet
from nova import debugger

if debugger.enabled():
    # turn off thread patching to enable the remote debugger
    eventlet.monkey_patch(os=False, thread=False)
else:
    eventlet.monkey_patch(os=False)
  • Monkey patching should also be done in a way that allows services to run without it:当 nova-api 运行在 apache 时,它依赖 apache 提供进程级别的并发,所以此时不应使用 monkey patch
  • Monkey patching with thread=False is likely to cause problems:我们在调试时,经常会设置 monkey_patch(thread=False),但是容易引起死锁问题,所以在生产环境中,切记设置 monkey_patch(thread=True)
  • Monkey patching can cause problems running flake8 with multiple workers
  • Mysql 的 Python driver: MySQL-Python 是一个成熟稳定的库,但是它使用了一些 C 库,故无法被 monkey patch,所以每当访问数据库时,其它的协程被阻塞,甚至有可能造成死锁。到 Kilo 版本时,社区选择由纯 Python 语言编写的 PyMySQL 作为 DB driver。