秒啊!Python 信号量源码拆解来了!

简介: 秒啊!Python 信号量源码拆解来了!

Signals简介

在类Unix系统上,信号用于将各种信息发送到正在运行的进程,它们来自用户命令,其他进程以及内核本身。所以信号是对已发生事件进程的通知,也可以被描述为软件中断,因为在大多数情况下,它们会中断程序的正常执行流程。

Linux信号

信号流转图

信号可以被内核或者进程发出,信号可以在子进程触发但是只能在主进程执行,具体信号流转图如下:

image.png

下面我们主要介绍内核发出的信号,具体以下几种情况内核可能会向进程发送信号 [2]:

  • When a hardware exception has occurred and that exception needs to be notified to the process. For eg. Attempting division by zero, or referencing the part of memory that is inaccessible.
  • Some software event occurred outside the process’s control but effects the process. For instance, input became available on a file descriptor, the terminal window got resized, process’s CPU time limit exceeded, etc.
  • User typed some terminal special characters like interrupt(Ctrl+C) or suspend character(Ctrl+Z).

信号位表示

Linux下可以通过/proc目录来确定进程对信号的处理方式 [3],下面是一个普通 Python 进程的采样

$ cat /proc/16107/status | grep Sig
SigQ:    0/257537
SigPnd:    0000000000000000
SigBlk:    0000000000000000
SigIgn:    0000000001001000
SigCgt:    0000000180000002
  • SigQ:number of signals queued/max. number for queue(since 5.12.0)
  • SigPnd:bitmap of pending signals for the thread
  • SigBlk: bitmap of blocked signals
  • SigIgn: bitmap of ignored signals
  • SigCgt:bitmap of caught signals

采样值均以十六进制表示,是一个bitmap,总共有 64(4*16) 种信号存在,具体信号表如下:

$ kill -l
 1) SIGHUP      2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT     7) SIGBUS       8) SIGFPE       9) SIGKILL      10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX

以上例中的SigCgt为例(转换成二级制后)

110000000000000000000000000000010
||                             `->    2=SIGINT
|`------------------------------->    32=SIGRTMIN-2
`-------------------------------->    33=SIGRTMIN-1
  • SIGINT:连接中断信号,程序终止(interrupt)信号,按下CTRL + C的时候触发。
  • SIGRTMIN-1SIGRTMIN-2:C库为NPTL保留的实时信号;具体查看 signal(7)

假如这会儿我们注册新的信号处理函数signal.signal(signal.SIGHUP, signal.default_int_handler),那么此时SigCgt的值会变成0000000180000003

Python信号处理机制

通过上面的学习,是否对Linux信号处理清晰了许多,下面我们将继续介绍Python信号处理源码实现,使用实践可参见之前内容《signal信号量使用详解 | Python基础》

代码架构

大体上,Python解释器对信号的实现总体思路比较简单。

  • 解释器负责与操作系统相关信号接口交互
  • 解释器实现信号处理接口模块(C)的加载
  • 解释器实现signal模块引用信号处理接口模块(C)

signal_python_architecture.png

源码解读

信号如何初始化

下面我们将从信号的初始化开始一点点探索signal模块源码的运作流程,源码来源于 python/cpython==v3.9.2 分支,具体流程图如下:

image.png

程序入口在Programs/python.c

// Programs/python.c
#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);         // 启动入口
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);    // 启动入口,与Py_Main类似,但argv参数是字节字符串数组
}
#endif

启动后可通过Py_MainPy_BytesMain进入Python环境真正入口pymain_main函数

// Modules/main.c
int
Py_Main(int argc, wchar_t **argv)
{
    // ...         // 不使用字符参数
    return pymain_main(&args);
}
// 或
int
Py_BytesMain(int argc, char **argv)
{
    // ...         // 使用字符参数
    return pymain_main(&args);
}
// 真正入口
static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);    // Python环境加载
    // ...
}

pymain_init函数实现了信号初始化加载,由于部分函数源码较长,后续只围绕signal相关源码解读

// Modules/main.c
static PyStatus
pymain_init(const _PyArgv *args)
{
    // ...
    PyConfig config;
    PyConfig_InitPythonConfig(&config);            // 加载信号量配置参数install_signal_handlers=1
    // ...
    status = Py_InitializeFromConfig(&config);    // 根据配置初始化Python环境
    // ...
}

Py_InitializeFromConfig函数包含多个调用,pyinit_main函数实现对解释器状态更新功能

// Python/pylifecycle.c
PyStatus
Py_InitializeFromConfig(const PyConfig *config)
{
    // ...
    if (config->_init_main) {    // 如果_init_main==0,在"main"阶段之前停止初始化
        status = pyinit_main(tstate);        // 实现对解释器状态更新功能
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }
    }
    // ...
}

pyinit_main中主要是一个函数调用,即函数init_interp_main

// Python/pylifecycle.c
static PyStatus
pyinit_main(PyThreadState *tstate)
{
    // ...
    PyStatus status = init_interp_main(tstate);
    // ...
}

init_interp_main函数包含很多功能初始化,此处只摘取signal相关部分源码

// Python/pylifecycle.c
static PyStatus
init_interp_main(PyThreadState *tstate)
{
    // ...
    if (is_main_interp) {    // 信号只能运行在主线程上
        if (_PySignal_Init(config->install_signal_handlers) < 0) {
            return _PyStatus_ERR("can't initialize signals");
        }
        // ...
    }
    // ...
}

_PySignal_Init调用signal_install_handlers函数触发了信号量加载,此处还可以发现之前加载的install_signal_handlers=1参数实际为信号量加载开关

// Modules/signalmodule.c
int
_PySignal_Init(int install_signal_handlers)
{
    // ...
    if (install_signal_handlers) {
        if (signal_install_handlers() < 0) {
            return -1;
        }
    }
    // ...
}

signal_install_handlers具体实现如下

// Modules/signalmodule.c
static int
signal_install_handlers(void)
{
#ifdef SIGPIPE
    PyOS_setsig(SIGPIPE, SIG_IGN);        // 忽略SIGPIPE信号
#endif
#ifdef SIGXFZ
    PyOS_setsig(SIGXFZ, SIG_IGN);        // 忽略SIGPIPE信号
#endif
#ifdef SIGXFSZ
    PyOS_setsig(SIGXFSZ, SIG_IGN);        // 忽略SIGPIPE信号
#endif

    // Import _signal to install the Python SIGINT handler
    PyObject *module = PyImport_ImportModule("_signal");    // 导入信号模块,此处触发Python信号的加载逻辑
    if (!module) {
        return -1;
    }
    Py_DECREF(module);

    return 0;
}

PyOS_setsig是对OS层信号量的封装,这块代码也可以解释为什么本文起始Linux示例中SigIgn为什么是0x0000000001001000

  • SIGPIPE:当进程试图写入数据到管道、FIFOSocket,但却没有相应的读取进程,会触发这个信号。通常是由于读取进程关闭了IPC通道的文件描述符而产生
  • SIGXFZ:这个没找到具体用途,尴尬脸TODO
  • SIGXFSZ:当进程试图使用write()truncate()函数,但却超出了进程的文件大小资源限制RLIMIT_FSIZE产生

signal_install_handlers通过PyImport_ImportModule导入了_signal信号模块,该结构体具体实现如下

// Modules/signalmodule.c
static struct PyModuleDef signalmodule = {
    PyModuleDef_HEAD_INIT,
    "_signal",        // 模块名字,import命令使用到
    module_doc,        // 模块的说明,XXX.__doc__ 将输出的内容
    -1,
    signal_methods,    // 模块中定义的函数列表
    NULL,
    NULL,
    NULL,
    NULL
};

怎样加载信号

接下来我们看看信号模块加载实现,信号的加载入口为PyInit__signal函数

// Modules/signalmodule.c
PyMODINIT_FUNC
PyInit__signal(void)
{
    PyObject *m, *d;
    int i;

    /* Create the module and add the functions */
    m = PyModule_Create(&signalmodule);
    if (m == NULL)
        return NULL;

#if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT)
    if (!initialized) {
        if (PyStructSequence_InitType2(&SiginfoType, &struct_siginfo_desc) < 0)
            return NULL;
    }
    Py_INCREF((PyObject*) &SiginfoType);
    PyModule_AddObject(m, "struct_siginfo", (PyObject*) &SiginfoType);    // 构建 PyModule_GetDict 执行结构体
    initialized = 1;
#endif

    /* Add some symbolic constants to the module */
    d = PyModule_GetDict(m);    // 获取 PyModuleObject 变量

    // 校验默认处理类型信号并分配默认处理的执行函数
    DefaultHandler = PyLong_FromVoidPtr((void *)SIG_DFL);
    if (!DefaultHandler ||
        PyDict_SetItemString(d, "SIG_DFL", DefaultHandler) < 0) {
        goto finally;    // 失败
    }
    // 校验忽略处理类型信号并分配忽略处理的执行函数
    IgnoreHandler = PyLong_FromVoidPtr((void *)SIG_IGN);
    if (!IgnoreHandler ||
        PyDict_SetItemString(d, "SIG_IGN", IgnoreHandler) < 0) {
        goto finally;
    }
    // 校验自定义处理类型信号并分配自定义处理的执行函数
    if (PyModule_AddIntMacro(m, NSIG))
        goto finally;

#ifdef SIG_BLOCK
    if (PyModule_AddIntMacro(m, SIG_BLOCK))    // 在原有掩码上添加pSet
         goto finally;
#endif
#ifdef SIG_UNBLOCK
    if (PyModule_AddIntMacro(m, SIG_UNBLOCK))    // 在原有源码上去除pSet
         goto finally;
#endif
#ifdef SIG_SETMASK
    if (PyModule_AddIntMacro(m, SIG_SETMASK))    // 设置掩码为pSet
         goto finally;
#endif
    // 获取signal模块中的默认处理函数,实际就是 signal_default_int_handler 
    IntHandler = PyDict_GetItemString(d, "default_int_handler");
    if (!IntHandler)
        goto finally;
    Py_INCREF(IntHandler);

    _Py_atomic_store_relaxed(&Handlers[0].tripped, 0);    // 
    // 循环初始化每个信号的Handlers,这个数组存储每个用户自定义的信号处理函数,以及标志是否发生该信号的标志
    for (i = 1; i < NSIG; i++) {
        void (*t)(int);
        t = PyOS_getsig(i);
        _Py_atomic_store_relaxed(&Handlers[i].tripped, 0);
        if (t == SIG_DFL)
            Handlers[i].func = DefaultHandler;
        else if (t == SIG_IGN)
            Handlers[i].func = IgnoreHandler;
        else
            Handlers[i].func = Py_None; /* None of our business */
        Py_INCREF(Handlers[i].func);
    }
    // 为 SIGINT 设置Python解释器的信号处理函数signal_handler
    // signal_handler 也会成为Python解释器与用户自定义处理函数的桥梁
    if (Handlers[SIGINT].func == DefaultHandler) {
        /* Install default int handler */
        Py_INCREF(IntHandler);
        Py_SETREF(Handlers[SIGINT].func, IntHandler);
        PyOS_setsig(SIGINT, signal_handler);
    }
// 添加 signal.SIGHUP 的定义(信号值和名称)
#ifdef SIGHUP
    if (PyModule_AddIntMacro(m, SIGHUP))
         goto finally;
#endif
#ifdef SIGINT
    if (PyModule_AddIntMacro(m, SIGINT))
         goto finally;
#endif
    // 添加 signal 模块中的各个 SIGXXX 的定义
    // ...    以此类推,这里不一一阐述
    if (PyErr_Occurred()) {
        Py_DECREF(m);
        m = NULL;
    }

  finally:
    return m;    // PyImport_ImportModule("_signal")方法取到的信号变量就是m
}

signal_methods的定义了模块中的函数列表,default_int_handler定义了默认执行函数,默认抛出KeyboardInterrupt

// Modules/signalmodule.c
static PyObject *
signal_default_int_handler(PyObject *self, PyObject *args)
{
    PyErr_SetNone(PyExc_KeyboardInterrupt);        // 抛出 KeyboardInterrupt
    return NULL;
}

Handlers是一个结构体数组,由信号的标志tripped和信号自定义处理函数func 组成,具体结构如下

// Modules/signalmodule.c
static volatile struct {
    _Py_atomic_int tripped;
    PyObject *func;
} Handlers[NSIG];

信号函数实现

我们先看一下Python中用于注册信号处理函数的signal.signal的实现

static PyObject *
signal_signal_impl(PyObject *module, int signalnum, PyObject *handler)
{
    PyObject *old_handler;
    void (*func)(int);
#ifdef MS_WINDOWS
    /* Validate that signalnum is one of the allowable signals */
    switch (signalnum) {    // 校验不被系统允许的信号
        case SIGABRT: break;
#ifdef SIGBREAK
        /* Issue #10003: SIGBREAK is not documented as permitted, but works
           and corresponds to CTRL_BREAK_EVENT. */
        case SIGBREAK: break;
#endif
        case SIGFPE: break;
        case SIGILL: break;
        case SIGINT: break;
        case SIGSEGV: break;
        case SIGTERM: break;
        default:
            PyErr_SetString(PyExc_ValueError, "invalid signal value");
            return NULL;
    }
#endif
    // 检测当前是否为主线程,这样解释为啥信号设置只能在主线程
    PyThreadState *tstate = _PyThreadState_GET();
    if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
        _PyErr_SetString(tstate, PyExc_ValueError,
                         "signal only works in main thread "
                         "of the main interpreter");
        return NULL;
    }
    // 信号值越界检测
    if (signalnum < 1 || signalnum >= NSIG) {
        _PyErr_SetString(tstate, PyExc_ValueError,
                         "signal number out of range");
        return NULL;
    }
    if (handler == IgnoreHandler) {            // 忽略执行函数定义
        func = SIG_IGN;
    }
    else if (handler == DefaultHandler) {    // 默认执行函数定义
        func = SIG_DFL;
    }
    else if (!PyCallable_Check(handler)) {    // 验证自定义执行函数是否可被调用
        _PyErr_SetString(tstate, PyExc_TypeError,
                         "signal handler must be signal.SIG_IGN, "
                         "signal.SIG_DFL, or a callable object");
        return NULL;
    }
    else {                // 自定义执行函数定义
        func = signal_handler;
    }

    if (_PyErr_CheckSignalsTstate(tstate)) {    // 在更改信号处理程序之前检查挂起信号标志位`is_tripped`,为1需重新调度,为0可以继续往下执行
        return NULL;
    }
    if (PyOS_setsig(signalnum, func) == SIG_ERR) {    // 假如信号处理程序设置失败
        PyErr_SetFromErrno(PyExc_OSError);
        return NULL;
    }

    old_handler = Handlers[signalnum].func;
    Py_INCREF(handler);
    Handlers[signalnum].func = handler;        // 记录自定义的函数到Handlers数组中

    if (old_handler != NULL) {    // 如果存在则返回老的执行函数
        return old_handler;
    }
    else {
        Py_RETURN_NONE;
    }
}

通常我们会自定义执行函数,具体如signal_handler

// Modules/signalmodule.c
static void
signal_handler(int sig_num)
{
    int save_errno = errno;

    trip_signal(sig_num);
    // ...
}

signal_handler里面的核心逻辑是触发trip_signal函数

// Modules/signalmodule.c
static void
trip_signal(int sig_num)
{
    unsigned char byte;
    int fd;
    Py_ssize_t rc;
    // 设置对应信号标志位置状态为1
    _Py_atomic_store_relaxed(&Handlers[sig_num].tripped, 1);

    /* Set is_tripped after setting .tripped, as it gets
       cleared in PyErr_CheckSignals() before .tripped. */
    _Py_atomic_store(&is_tripped, 1);    // 挂起信号标志位 is_tripped

    /* Signals are always handled by the main interpreter */
    PyInterpreterState *interp = _PyRuntime.interpreters.main;

    // 发送消息给解释器即可,这个为核心函数下面细讲
    _PyEval_SignalReceived(interp);
    // ...
}

解释器处理逻辑

解释器执行信息核心部分位于 _PyEval_SignalReceivedwakeup_fd默认为INVALID_FD,用户可以通过Python接口函数signal.set_wakeup_fd对其进行设置

// Python/ceval.c
void
_PyEval_SignalReceived(PyInterpreterState *interp)
{
#ifdef MS_WINDOWS
    // bpo-42296: On Windows, _PyEval_SignalReceived() is called from a signal
    // handler which can run in a thread different than the Python thread, in
    // which case _Py_ThreadCanHandleSignals() is wrong. Ignore
    // _Py_ThreadCanHandleSignals() and always set eval_breaker to 1.
    //
    // The next eval_frame_handle_pending() call will call
    // _Py_ThreadCanHandleSignals() to recompute eval_breaker.
    int force = 1;
#else
    int force = 0;
#endif
    /* bpo-30703: Function called when the C signal handler of Python gets a
       signal. We cannot queue a callback using _PyEval_AddPendingCall() since
       that function is not async-signal-safe. */
    SIGNAL_PENDING_SIGNALS(interp, force);
}
// Python/ceval.c
static inline void
SIGNAL_PENDING_SIGNALS(PyInterpreterState *interp, int force)
{
    struct _ceval_runtime_state *ceval = &interp->runtime->ceval;
    struct _ceval_state *ceval2 = &interp->ceval;
    _Py_atomic_store_relaxed(&ceval->signals_pending, 1);
    if (force) {
        _Py_atomic_store_relaxed(&ceval2->eval_breaker, 1);
    }
    else {
        /* eval_breaker is not set to 1 if thread_can_handle_signals() is false */
        COMPUTE_EVAL_BREAKER(interp, ceval, ceval2);
    }
}
// Python/ceval.c
/* This can set eval_breaker to 0 even though gil_drop_request became
   1.  We believe this is all right because the eval loop will release
   the GIL eventually anyway. */
static inline void
COMPUTE_EVAL_BREAKER(PyInterpreterState *interp,
                     struct _ceval_runtime_state *ceval,
                     struct _ceval_state *ceval2)
{
    _Py_atomic_store_relaxed(&ceval2->eval_breaker,
        _Py_atomic_load_relaxed(&ceval2->gil_drop_request)
        | (_Py_atomic_load_relaxed(&ceval->signals_pending)
           && _Py_ThreadCanHandleSignals(interp))
        | (_Py_atomic_load_relaxed(&ceval2->pending.calls_to_do)
           && _Py_ThreadCanHandlePendingCalls())
        | ceval2->pending.async_exc);
}

这一系列的操作最终影响下一次解释器循环,那解释器具体怎么处理呢?_PyEval_EvalFrameDefault为解释器的核心函数,主要做循环处理执行命令操作(3.9.2版本的_PyEval_EvalFrameDefault优化后流程比较复杂,后续单独抽一篇详细拆解)

// Python/ceval.c
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
// ...
main_loop:
    for (;;) {
        assert(stack_pointer >= f->f_valuestack); /* else underflow */
        assert(STACK_LEVEL() <= co->co_stacksize);  /* else overflow */
        assert(!_PyErr_Occurred(tstate));

        /* Do periodic things.  Doing this every time through
           the loop would add too much overhead, so we do it
           only every Nth instruction.  We also do it if
           ``pending.calls_to_do'' is set, i.e. when an asynchronous
           event needs attention (e.g. a signal handler or
           async I/O handler); see Py_AddPendingCall() and
           Py_MakePendingCalls() above. */

        if (_Py_atomic_load_relaxed(eval_breaker)) {
            // ...
            if (eval_frame_handle_pending(tstate) != 0) {    // 解释器处理信号
                goto error;
            }
        }

    fast_next_opcode:
// ...

解释器设计信号部分主要在eval_frame_handle_pending函数里面完成

// Python/ceval.c
/* Handle signals, pending calls, GIL drop request
   and asynchronous exception */
static int
eval_frame_handle_pending(PyThreadState *tstate)
{
    _PyRuntimeState * const runtime = &_PyRuntime;
    struct _ceval_runtime_state *ceval = &runtime->ceval;

    /* Pending signals */
    if (_Py_atomic_load_relaxed(&ceval->signals_pending)) {    // 处理信号状态
        if (handle_signals(tstate) != 0) {
            return -1;
        }
    }

    /* Pending calls */
    struct _ceval_state *ceval2 = &tstate->interp->ceval;
    if (_Py_atomic_load_relaxed(&ceval2->pending.calls_to_do)) { // 处理回调
        if (make_pending_calls(tstate) != 0) {
            return -1;
        }
    }
    // ...
}

make_pending_calls函数为具体的信号处理逻辑

// Python/ceval.c
static int
make_pending_calls(PyThreadState *tstate)
{
    assert(is_tstate_valid(tstate));

    /* only execute pending calls on main thread */
    if (!_Py_ThreadCanHandlePendingCalls()) {
        return 0;
    }

    /* don't perform recursive pending calls */
    static int busy = 0;
    if (busy) {
        return 0;
    }
    busy = 1;

    /* unsignal before starting to call callbacks, so that any callback
       added in-between re-signals */
    UNSIGNAL_PENDING_CALLS(tstate->interp);
    int res = 0;

    /* perform a bounded number of calls, in case of recursion */
    struct _pending_calls *pending = &tstate->interp->ceval.pending;
    for (int i=0; i<NPENDINGCALLS; i++) {
        int (*func)(void *) = NULL;
        void *arg = NULL;

        /* pop one item off the queue while holding the lock */
        PyThread_acquire_lock(pending->lock, WAIT_LOCK);
        _pop_pending_call(pending, &func, &arg);
        PyThread_release_lock(pending->lock);

        /* having released the lock, perform the callback */
        if (func == NULL) {
            break;
        }
        res = func(arg);
        if (res) {
            goto error;
        }
    }

    busy = 0;
    return res;

error:
    busy = 0;
    SIGNAL_PENDING_CALLS(tstate->interp);
    return res;
}

Python中维持了一个_pending_calls数组,可以将ceval.c文件里解释器循环流程for (;;))中调用的函数放在里面,具体结构如下

// Include/internal/pycore_interp.h
struct _pending_calls {
    PyThread_type_lock lock;
    /* Request for running pending calls. */
    _Py_atomic_int calls_to_do;
    /* Request for looking at the `async_exc` field of the current
       thread state.
       Guarded by the GIL. */
    int async_exc;
#define NPENDINGCALLS 32
    struct {
        int (*func)(void *);
        void *arg;
    } calls[NPENDINGCALLS];
    int first;
    int last;
};

由上,我们完成了信号从初始化默认加载自定义函数配置解释器循环执行整个流程

总结一下

  • 当接收到信号时会保存当前上下文,然后调用注册的信号处理函数trip_signal。此时通过设置Handlers数组中对应信号的标志位来标记信号被触发,并且通过make_pending_calls()更改解释器的状态变量。解释器在执行下一条opcode时会检测状态变量,遍历Handlers执行所有已触发信号的处理函数。
  • Python信号处理程序总是在主Python线程中执行,即使信号是在另一个线程中接收的。此外,只有主线程被允许设置一个新的信号处理器。
  • 如果发送多次信号可能只会调用一次信号处理函数

参考文献

最后最后

❤️❤️❤️读者每一份热爱都是笔者前进的动力!

我是三十一,感谢各位朋友:点赞、收藏和评论,我们下期再见!

相关文章
|
9天前
|
机器学习/深度学习 数据采集 数据可视化
【python】python当当数据分析可视化聚类支持向量机预测(源码+数据集+论文)【独一无二】
【python】python当当数据分析可视化聚类支持向量机预测(源码+数据集+论文)【独一无二】
|
8天前
|
JSON 算法 API
京东以图搜图功能API接口调用算法源码python
京东图搜接口是一款强大工具,通过上传图片即可搜索京东平台上的商品。适合电商平台、比价应用及需商品识别服务的场景。使用前需了解接口功能并注册开发者账号获取Key和Secret;准备好图片的Base64编码和AppKey;生成安全签名后,利用HTTP客户端发送POST请求至接口URL;最后解析JSON响应数据以获取商品信息。
|
8天前
|
开发者 Python
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
32 1
|
8天前
|
开发者 Python
深入解析Python `requests`库源码,揭开HTTP请求的神秘面纱!
深入解析Python `requests`库源码,揭开HTTP请求的神秘面纱!
21 1
|
8天前
|
数据可视化 数据挖掘 索引
【python】Python马铃薯批发市场交易价格数据分析可视化(源码+数据集)【独一无二】
【python】Python马铃薯批发市场交易价格数据分析可视化(源码+数据集)【独一无二】
|
8天前
|
数据可视化 数据挖掘 数据处理
【python】python农产品数据分析可视化(源码+论文+数据)【独一无二】
【python】python农产品数据分析可视化(源码+论文+数据)【独一无二】
|
9天前
|
数据采集 数据可视化 Python
【python】python猫眼电影数据抓取分析可视化(源码+数据集+论文)【独一无二】
【python】python猫眼电影数据抓取分析可视化(源码+数据集+论文)【独一无二】
|
8天前
|
机器学习/深度学习 数据采集 数据可视化
【python】python心理健康医学数据分析与逻辑回归预测(源码+数据集+论文)【独一无二】
【python】python心理健康医学数据分析与逻辑回归预测(源码+数据集+论文)【独一无二】
|
8天前
|
存储 数据可视化 数据挖掘
【python】Python考研分数 线性回归模型预测(源码+论文)【独一无二】
【python】Python考研分数 线性回归模型预测(源码+论文)【独一无二】
|
8天前
|
存储 数据可视化 数据挖掘
【Python】Tkinter电器销售有限公司销售数据分析(源码)【独一无二】
【Python】Tkinter电器销售有限公司销售数据分析(源码)【独一无二】