比Mojo慢68000倍,Python性能差的锅该给GIL吗?

2023-11-11

a3836086c843db7a78dbdf8e706b42ba.gif# 关注并星标腾讯云开发者

# 每周1 | 鹅厂工程师带你审判技术

# 第3期 | 李志瑞:天使还是魔鬼?聊聊 Python GIL

2a5361614a2c6aec90de54eae460a73f.png

9 月 7 日,新兴编程语言 Mojo 正式发布。Mojo 的最初设计目标是比 Python 快 35000 倍,近期该团队表示,因为结合了动态与静态语言的优点,Mojo 一举将性能提升到了 Python 的 68000 倍。腾讯工程师此前也曾试用 Python 并做了相关评测,参考:《放弃Python拥抱Mojo?鹅厂工程师真实使用感受》

这不是第一个号称比 Python 更快的编程语言,相信也不会是最后一个。那么问题来了,为什么是个编程语言就比 Python 快呢?Python 在高性能、多线程方面为什么这么为人诟病?本文将以 Python PEP 703 草案的相关内容为核心,分析个中原因。


在学习 Python 的时候,相信大家应该都会了解到类似「Python 的多线程是伪多线程」、「Python 并不能通线程发挥多核 CPU 性能」这样的说法,导致 Python 这些问题的原因就是 Python 的 GIL,即全局解释器锁(global interpreter lock)。

这里需要明确的一点是,从 Python 语言标准的角度看,GIL 并不是必须的,但 Python 的默认实现是 CPython,这是我们去官网下载 Python 时获得的默认实现,也是绝大多数 Python 用户使用的实现。是 CPython 虚拟机使用了 GIL 而非 Python 语言要求要有 GIL。除了 CPython 之外,社区也有各种不同的 Python 实现,例如 JVM 上的 Jython,.Net 上的 IronPython 等,它们都没有 GIL。也就是说,如果你喜欢,可以用任意语言在任意平台上自己实现一个符合标准并且不带 GIL 的 Python。但由于最广为使用的 Python 实现是 CPython,因此 GIL 造成的局限性仍然在很多语境下被认为是 Python 的局限性。

ad09bd5e42679f6bbc5a754e6639301c.png

要理解 GIL 对 Python 的影响,我们首先要先明白 GIL 到底是什么以及它是如何工作的。

简单来说,GIL 就是一个 Python 虚拟机中的全局锁,它使得同一时刻只有一个线程能够获得 Python 虚拟机的控制权,防止多个线程同时执行 Python 字节码。在 Python 中,每个线程在执行 Python 字节码的时候都需要持有 GIL,这意味着,多个线程的 Python 字节码解释事实上会被 GIL 强制变为串行执行。那么,这个切换过程是如何发生的呢?事实上,GIL 的实现也随着 Python 的发展发生过明显的变化。在 Python 3.2 之前,Python 虚拟机主要是基于 tick 数来控制 GIL 切换,默认情况下,一个线程在执行 100 条字节码后就会释放 GIL,其他线程就可以去抢占 GIL,如下图所示:

c6c155c252d289adc1a0300b4f83b77a.png

除了 tick 之外,一些阻塞操作,例如 send、recv、sleep 也会触发 GIL 的主动释放,例如 timemodule.c 中的 pysleep 代码:

static int pysleep(_PyTime_t timeout) {
    ...
        int ret;
        Py_BEGIN_ALLOW_THREADS
        ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &timeout_abs, NULL);
        err = ret;
        Py_END_ALLOW_THREADS
    ...
}

这里将实际的 sleep 调用使用 Py_BEGIN_ALLOW_THREADS 和 Py_END_AL‍LOW_THREADS 包裹了起来,这两个宏是 Python 扩展模块开发中会常用到的一个模式,用来显式释放 GIL,允许其他线程继续执行,执行完毕后,再获取 GIL 继续后面的逻辑。

上面这个基于 tick 的 GIL 调度实现容易导致在其他核心上执行的线程频繁尝试抢占 GIL, 造成 CPU 的空转。另外,由于正在工作的线程达到约定 tick 数时会先释放锁,然后立刻再去抢锁,因此这里很容易出现该线程重复抢到锁使得其他线程饥饿的情况,显然这个调度算法并不合理。关于这一点,David Beazley 在 PyCon 2010 作过一个关于 GIL 的分享,可以参考其中的数据。

在 Python 3.2 后,GIL 持有的调度改为基于时间片。等待 GIL 的线程在等待超时后,将一个名为 gil_drop_request 原子全局变量的值设置为 1,通过这个方式来通知当前的工作线程释放 GIL。而当前工作线程会去检查这个值,并在释放 GIL 后,通过条件变量通知等待中的线程 GIL 已经被释放,这既避免了等待中的线程频繁去尝试抢锁,也避免了该线程重复获得锁引发的其他线程饥饿问题:

b3fea0126c23a68f50775bb09a70d5e5.png

我们可以很清晰地从 ceval.c 代码中看到这个逻辑。Python 虚拟机在一个巨大的 eval 循环中持续执行 Python 字节码:

PyObject* _PyEval_EvalFrameDefault(...) {
    // 计算主循环
main_loop:
    for (;;) {
        if (_Py_atomic_load_relaxed(eval_breaker)) {
            opcode = _Py_OPCODE(*next_instr);
            // 对于一些特定的字节码不做任何检查就继续执行
            if (...) {
                goto fast_next_opcode;
            }


            // 检查是否需要处理正在等待的任务
            if (eval_frame_handle_pending(tstate) != 0) {
                goto error;
            }
        }
    }
}

在 eval_frame_handle_pending 中,Python 虚拟机会检查 gil_drop_request 的值,并根据需要释放 GIL:

static int eval_frame_handle_pending(PyThreadState *tstate) {
    _PyRuntimeState * const runtime = &_PyRuntime;
    struct _ceval_runtime_state *ceval = &runtime->ceval;
    ...
    // 检查 gil_drop_request 是否被设为 1
    if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request)) {
        // 释放 GIL
        drop_gil(ceval, ceval2, tstate);
        // 此时其他线程就可以开始执行了


        // 重新获取 GIL
        take_gil(tstate);
    }
    ...
}

默认情况下,Python 每 5ms 会在线程间切换一次,我们可以通过 getswitchinterval 来获取当前的切换间隔时间,通过 setswitchinterval 来设置间隔时间(单位为秒):

>>> import sys
>>> sys.getswitchinterval()
0.005
>>> sys.setswitchinterval(1)
>>> sys.getswitchinterval()
1.0

be49ea47c0fdf3d1d058278e98b2300a.png

了解 GIL 是如何工作的之后,我们来看一下 GIL 如何影响我们现有的程序实现方式。

对于 Python 程序来说,既然 GIL 使单个虚拟机无法并行执行 Python 程序,那么在需要充分利用多核 CPU 的场景中,最常见的做法就是启动多个 Python 进程,这也是 Python 服务器的常见实现方式,例如:

from multiprocessing import Process, Pipe


def f(conn):
    conn.send([42, None, 'hello'])
    conn.close()


if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())   # prints "[42, None, 'hello']"
    p.join()

此时,由于每个进程是独立的 Python 虚拟机,因此 GIL 也是相互独立的,互不影响,以此实现了并行计算。但这里存在一个问题,由于进程间无法简单共享对象,因此进程间通信需要进行对象的序列化和反序列化操作,这造成了明显的计算开销。另外,实现这样的逻辑也不可避免地增加了程序的复杂性。

对于 Python 的扩展模块开发者而言,情况会稍微好一些。一方面,由于 GIL 的存在,扩展模块的开发者可以简单地直接操作数据而不用担心并发导致的数据出错,这降低了心智负担。另一方面,如果希望实现并行化计算,开发者可以在扩展中启动多个系统线程,此时,这些线程的执行是不受 GIL 限制的。另外,就像之前我们看到的那样,扩展的实现甚至可以在执行复杂计算任务的时候将 GIL 释放掉,允许 Python 虚拟机继续执行其他逻辑,等待繁重的计算任务结束后或者 IO 数据加载好后再重新获取 GIL 再将数据返回,这是许多科学计算库常用的解决方案。但这里还是存在一个问题,如果这个扩展模块与 Python 虚拟机无关的单次操作时间没有那么长,而是需要不断去操作 Python 虚拟机内部的数据,那么即便开发者注意去释放了 GIL,在程序执行到需要操作 Python 虚拟机内部数据的逻辑时,它还是需要频繁去获取 GIL,这又使得 GIL 对性能的影响变得无法忽略了。

很显然,这个 GIL 还真是为开发者造成了不少麻烦。看到这里,你也许想问,既然这个 GIL 有这么多问题,Python 的创始人 Guido van Rossum 为什么当初要这样设计呢?

d93352b77de88804b33786ea711ca7a6.png

当我们考虑一个技术选择是否合理的时候,不能只以当下的目光去审视过去,而需要回到当时的场景中去考虑问题。从 Python 的大事件时间轴中可以看到,我们正在谈论的对象是一个从 1989 年就开始设计的语言,到 1991 年的时候就出现了第一个 release 版本(0.9.0),到 2000 年的时候,Python 2 就发布了。而世界上第一个多核 CPU 是 2001 年的 IBM Power4,也就是说,在 Python 最初被设计时,并没有多核 CPU 这种东西,后来即便有了,也并不普及。因此 CPython 在实现的时候并没有作过并发和并行计算这方面的考虑。

不过在 Python 面世后的几年内,当时一些操作系统就开始逐步提供多线程能力了。此时,Python 自然也想要支持多线程,但由于 Python 虚拟机的实现一开始并没有并发安全的考虑,因此要实现完美的多线程支持需要重构整个虚拟机,这不仅是工作量大的问题,而且短时间内很难保证软件质量。因此 Python 采用了一个取巧的方法,就是加入 GIL。有了 GIL 后,Python 就能为用户提供了一个类似操作系统多线程的能力,如果用户使用的硬件是单核 CPU,那这个多线程表现得和操作系统的多线程差不多,多个线程之间可以切换,分时复用 CPU 资源,共同完成工作,看起来就像是它们在多核 CPU 上执行一样。这对于当时的用户群来说确实完全够用了,因为当时大多数用户使用的都是单核 CPU。而且,GIL 的出现使得一些线程不安全的 C 模块能很容易地被暴露给 CPython 调用而不用担心各种隐蔽的问题,这样的表现对于一个胶水语言来说实在是无可挑剔。尤其在那个 C 语言具有极强统治力的时代里,这样简单易用的优势在很大程度上促进了 Python 的流行。从这个角度来说,GIL 对于 Python 的发展可谓是功不可没。

当然,当时决策的历史局限性会在时间往前推进的过程中越发明显,尤其是到了摩尔定律终结的时候:

7989b82d415a72c3ebea8da7afb5e0ec.png

我们知道,CPU 的单核计算能力并没有如摩尔定律预言的那样无限提高,CPU 的性能逐渐显露出瓶颈,除了针对特殊用途定制特定芯片之外,加强计算能力的渠道就逐步从依赖提高单核计算能力的提升转变为将芯片做小并增加核心数量。硬件的风向转变影响了软件开发,开发者们希望 Python 也能提供充分利用多核硬件来实现并行计算的能力。因此 GIL 带来的约束就使它变得更加臭名昭著了,它也就从促进 Python 流行的天使慢慢变为限制 Python 发展的魔鬼。

话虽如此,但 GIL 也并不是想去掉就能去掉。Guido van Rossum 曾在博客《It isn't Easy to Remove the GIL》中正面回应过为什么不从 CPython 中去掉 GIL,这里主要提到了两个问题:

  • 很难做到在移除 GIL 后依然让 Python 虚拟机保持移除前的单核性能

  • 移除 GIL 会令 Python 扩展模块的开发变复杂,因为所有的扩展模块都需要考虑被多线程调用的情况了

另外,在 Python 社区中,CPython 的维护者对于去掉 GIL 的提案还有一些明确的约束:

  • 简单:不能让 CPython 变得不可维护

  • 并发:能确实提高多线程性能

  • 速度:不能降低单线程性能

  • 特性:必须完整实现目前 CPython 的所有特性,包括 __del__ 和弱引用

  • API 兼容:和目前的 CPython 扩展所使用的宏源码兼容

  • ……

可以看出,移除 GIL 的提案其实非常难以获得通过。一件显而易见的事情是,针对多线程的架构在单线程下非常难以达到和针对单线程的架构同样的执行效率,因此这对于实现者来说本身就具有非常大的挑战。一个典型例子是 CPython 的 GC 实现,目前 CPython GC 的方案是以引用计数为主,辅以标记清除来处理循环引用的情况。每个 Python 对象在 CPython 虚拟机内大致表示为这样的形式:

struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
};

可以看到,这个引用计数 ob_refcnt 就是一个裸的整型值。由于 GIL 的存在,虚拟机在和扩展模块在操作对象的引用计数时并不需要额外加锁。这不仅使得 Python 在单线程下在处理引用计数的增减非常高效,而且从根源上避免了死锁。如果想要去掉 GIL,那就必须考虑到对引用计数的并发访问,在这种情况下,无论是细粒度的锁还是对引用计数进行原子操作,都会或多或少造成一定的性能开销。事实上,早在 1999 年,Greg Stein 就实现了一个 CPython 的分支,用细粒度的锁替换了 GIL,结果使得单核执行效率减半了,这个修改自然也就不了了之。

另外,我们从上面的描述中也能看出,在经历了从 Python 2 到 Python 3 的剧痛之后,整个 Python 社区都很明确地知道了永远不要低估用户进行版本迁移的成本这件事,因此修改的兼容性被放在了非常重要的位置。由于 GIL 存在已久,不仅是 CPython 虚拟机本身,许多扩展模块都已经非常依赖于 GIL,因此去除 GIL 很有可能对这些模块产生影响。因此可以预见的是这个升级的过程可能并不会那么顺利,别看现在大家对 GIL 抱怨不断,等真能去除 GIL 的时候,有多少用户真去升级现有版本呢,又有多少现有的扩展模块去适配新的规范呢?这些都很难说。Python 社区自然也非常清楚这一点,因此 Sam Gross 在 PEP 703 中提出的建议是为 CPython 增加一个编译参数 --disable-gil,以便让有需要的用户逐步迁移。这当然是一个稳健的做法,不过这个过渡时间有多久,就还真是一个未知数了,需要实践之后才能知道结果。

ffa4e9c6e3df1e8f0a416531f6a4e5a8.png

Python GIL 并不像很多人想象的那样是一个愚蠢的决策,在当时的时代背景下,它很大程度促进了 Python 的发展和生态的繁荣。只是随着时代的变迁,完成历史使命的它也到了逐渐退出历史舞台的时候了。虽然这个过程无法一蹴而就,但目前渐进的改变更能保障兼容性,让用户有一个平滑过渡的机会。然而编程语言届从来都没有风平浪静的一天,想要挑战 Python 地位的竞争者不断出现,例如前文提到的 Mojo 就号称兼容 Python 并巨幅提升性能,至于 Python 的未来会如何,还请拭目以待。

-End-

原创作者|李志瑞

你是否支持 Python 中移除 GIL,为什么?欢迎再评论区分享。我们将选取1则最有意义的评论,送出腾讯云开发者-便携通勤袋1个(见下图)。9月18日中午12点开奖。

584c3da4404af8a02c533d7580c50d80.png

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

比Mojo慢68000倍,Python性能差的锅该给GIL吗? 的相关文章

  • Spark 请求最大计数

    我是 Spark 的初学者 我尝试请求允许我检索最常访问的网页 我的要求如下 mostPopularWebPageDF logDF groupBy webPage agg functions count webPage alias cntW
  • Python - 将宽字符字符串从二进制文件转换为 Python unicode 字符串

    这是漫长的一天 我有点困惑 我正在读取一个包含大量宽字符字符串的二进制文件 我想将它们转储为 Python unicode 字符串 为了解压非字符串数据 我使用 struct 模块 但我不知道如何对字符串执行相同的操作 例如 阅读 系列 一
  • 我怎样才能更多地了解Python的内部原理? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我使用Python编程已经有半年多了 我对Python内部更感兴趣 而不是使用Python开发应用程序
  • 如何使用 Plotly 中的直方图将所有离群值分入一个分箱?

    所以问题是 我可以在 Plotly 中绘制直方图 其中所有大于某个阈值的值都将被分组到一个箱中吗 所需的输出 但使用标准情节Histogram类我只能得到这个输出 import pandas as pd from plotly import
  • 如何在 pytest 中将单元测试和集成测试分开

    根据维基百科 https en wikipedia org wiki Unit testing Description和各种articles https techbeacon com devops 6 best practices inte
  • Pandas 中允许重复列

    我将一个大的 CSV 包含股票财务数据 文件分割成更小的块 CSV 文件的格式不同 像 Excel 数据透视表之类的东西 第一列的前几行包含一些标题 公司名称 ID 等在以下列中重复 因为一家公司有多个属性 而不是一家公司只有一栏 在前几行
  • 切片 Dataframe 时出现 KeyError

    我的代码如下所示 d pd read csv Collector Output csv df pd DataFrame data d dfa df copy dfa dfa rename columns OBJECTID Object ID
  • 对图像块进行多重处理

    我有一个函数必须循环遍历图像的各个像素并计算一些几何形状 此函数需要很长时间才能运行 在 24 兆像素图像上大约需要 5 小时 但似乎应该很容易在多个内核上并行运行 然而 我一生都找不到一个有据可查 解释充分的例子来使用 Multiproc
  • TensorFlow的./configure在哪里以及如何启用GPU支持?

    在我的 Ubuntu 上安装 TensorFlow 时 我想将 GPU 与 CUDA 结合使用 但我却停在了这一步官方教程 http www tensorflow org get started os setup md 这到底是哪里 con
  • 将 matplotlib 颜色图集中在特定值上

    我正在使用 matplotlib 颜色图 seismic 绘制绘图 并且希望白色以 0 为中心 当我在不进行任何更改的情况下运行脚本时 白色从 0 下降到 10 我尝试设置 vmin 50 vmax 50 但在这种情况下我完全失去了白色 关
  • 如何使用列表作为pandas数据框中的值?

    我有一个数据框 需要列的子集包含具有多个值的条目 下面是一个带有 运行时 列的数据框 其中包含程序在各种条件下的运行时 df condition a runtimes 1 1 5 2 condition b runtimes 0 5 0 7
  • 如何在 OSX 上安装 numpy 和 scipy?

    我是 Mac 新手 请耐心等待 我现在使用的是雪豹 10 6 4 我想安装numpy和scipy 所以我从他们的官方网站下载了python2 6 numpy和scipy dmg文件 但是 我在导入 numpy 时遇到问题 Library F
  • 使用 PyTorch 分布式 NCCL 连接失败

    我正在尝试使用 torch distributed 将 PyTorch 张量从一台机器发送到另一台机器 dist init process group 函数正常工作 但是 dist broadcast 函数中出现连接失败 这是我在节点 0
  • Tkinter - 浮动窗口 - 调整大小

    灵感来自this https stackoverflow com a 22424245 13629335问题 我想为我的根窗口编写自己的调整大小函数 但我刚刚注意到我的代码显示了一些性能问题 如果你快速调整它的大小 你会发现窗口没有像我希望
  • 当鼠标悬停在上面时,intellisense vscode 不显示参数或文档

    我正在尝试将整个工作流程从 Eclipse 和 Jupyter Notebook 迁移到 VS Code 我安装了 python 扩展 它应该带有 Intellisense 但它只是部分更糟糕 我在输入句点后收到建议 但当将鼠标悬停在其上方
  • 具有自定义值的 Django 管理外键下拉列表

    我有 3 个 Django 模型 class Test models Model pass class Page models Model test models ForeignKey Test class Question model M
  • 如何读取Python字节码?

    我很难理解 Python 的字节码及其dis module import dis def func x 1 dis dis func 上述代码在解释器中输入时会产生以下输出 0 LOAD CONST 1 1 3 STORE FAST 0 x
  • 迭代 pandas 数据框的最快方法?

    如何运行数据框并仅返回满足特定条件的行 必须在之前的行和列上测试此条件 例如 1 2 3 4 1 1 1999 4 2 4 5 1 2 1999 5 2 3 3 1 3 1999 5 2 3 8 1 4 1999 6 4 2 6 1 5 1
  • 您可以使用关键字参数而不提供默认值吗?

    我习惯于在 Python 中使用这样的函数 方法定义 def my function arg1 None arg2 default do stuff here 如果我不供应arg1 or arg2 那么默认值None or default
  • Scrapy Spider不存储状态(持久状态)

    您好 有一个基本的蜘蛛 可以运行以获取给定域上的所有链接 我想确保它保持其状态 以便它可以从离开的位置恢复 我已按照给定的网址进行操作http doc scrapy org en latest topics jobs html http d

随机推荐

  • 深入了解Java队列接口

    队列接口 队列接口是 Java 集合框架的一个重要部分 它扩展了 Collection 接口 队列接口表示遵循 先进先出 FIFO 原则的元素集合 队列允许存储重复值 队列的基本特征是元素按特定顺序存储 类似于等待轮流的人群 主要通过添加元
  • 服务器文件怎么删,怎么删除服务器文件

    怎么删除服务器文件 内容精选 换一换 执行chmod R 777 导致CentOS云服务器根目录权限设置成777 系统中的大部分服务以及命令无法使用 此时可通过系统自带的getfacl命令来拷贝和还原系统权限 本节操作介绍误操作导致根目录设
  • Android 12应用适配指南

    Android 12应用适配指南 1 Android 12上的主要变更 1 1 兼容性 1 1 1 前台服务启动限制 1 1 2 前台服务通知延迟 1 1 3 待处理 intent 必须声明可变性 1 1 4 非SDK接口名单更新 1 2
  • vue 通过逻辑控制el-dropdown组件展开和收起

    el dropdown在element ui中应用场景并不少 有些时候 我们下拉的内容并不是他本来的选项 用户选择后就没有关闭 这个时候 就需要我们在逻辑层开启或关闭他的菜单
  • 通过JVM深入理解Java异常机制

    JVM内部结构 要深入理解JVM异常处理机制 需要从JVM内部结构开始 下图描述的主要是Java程序在执行时 由JVM管理的运行时数据区 包括方法区 Java堆 Java虚拟机栈 PC寄存器 本地方法栈 还有常量池 它们又被分为两大类 线程
  • matlab-基础 plot xlabel 图像加上x,y轴的标签

    2019独角兽企业重金招聘Python工程师标准 gt gt gt matlab R2018a 64bit OS Windows 10 x64typesetting Markdown blog my oschina net zhicheng
  • 2014百度校招笔试题之动态链接库&静态链接库详解

    1 什么是静态连接库 什么是动态链接库 静态链接库用通俗的话讲 静态库就是将代码编译到一个二进制文件下 通常扩展名为 LIB 然后客户端调用程序 只需要包含相关的 h文件及LIB库文件一起链接到exe文件中 可执行程序发布后 不再需要该 l
  • cookie默认有效期多长_惊艳面试官的 Cookie 介绍

    关注在看 以后更多干货分享在头条 Cookie 是什么 Cookie 是用户浏览器保存在本地的一小块数据 它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上 Cookie 主要用于以下三个方面 会话状态管理 如用户登录状态 购物
  • 亲测GO环境搭建,理解go build、go install、go get

    前言 秉承着作为一个新人 要有着 指哪打哪 的觉悟 在老大的需求下 顶上前端的空缺先干阵子前端 当然 在工作之余还是要好好朝着既定的方向努力的 实习结束后还了电脑 这次毕业再来换了新Mac 重新搭下Go的环境 顺便好好总结下 以后随着学习的
  • VMware虚拟机安装教程

    虚拟机 Virtual Machine 是通过软件模拟的完整计算机系统 在实体计算机中能够完成的工作在虚拟机中都能够实现 在计算机中创建虚拟机时 需要将实体机的部分硬盘和内存容量作为虚拟机的硬盘和内存容量 每个虚拟机都有独立的CMOS 硬盘
  • vue使用three.js并导入.obj模型

    下载three js等依赖 npm install three js data下面定义的 canvasDom null renderer null scene null camera null controls null 在需要用到thre
  • android 日历控件_UI界面开发工具Calendar日历插件示例合集

    适用于Visual C MFC ActiveX COM的Calendar控件为Windows开发人员提供了Outlook样式的日历和日期选择组件 您可以轻松创建日历 可以管理按日 周 工作周或月安排的约会 并提供了多个选项供您选择 包括集成
  • 离线安装Nginx(rpm方式)

    环境 centos7 9 下面将展示通过rpm方式安装Nginx 1 官方下载Nginx rpm包 下载地址 http nginx org packages 下载版本根据操作系统版本进行选择 本次操作系统是centos7 9 64位系统 所
  • dbcp

    initialSize 10 初始化连接 连接池启动时创建的初始化连接数量 默认值为0 maxActive 80 最大活动连接 连接池中可同时连接的最大的连接数 默认值为8 minIdle 10 最小空闲连接 连接池中最小的空闲的连接数 低
  • 30个Python极简代码

    Python 是机器学习最广泛采用的编程语言 它最重要的优势在于编程的易用性 如果读者对基本的 Python 语法已经有一些了解 那么这篇文章可能会给你一些启发 作者简单概览了 30 段代码 它们都是平常非常实用的技巧 我们只要花几分钟就能
  • webpack 模块加载兼打包工具——入门或进阶

    一 说点废话 1 webpack 是以 commonJS 的形式来书写脚本滴 但对 AMD CMD 的支持也很全面 方便旧项目进行代码迁移 2 能被模块化的不仅仅是 JS 了 3 开发便捷 能替代部分 grunt gulp 的工作 比如打包
  • 【Linux】Linux编程之 mmap解析

    前言 虚拟内存系统通过将虚拟内存分割为称作虚拟页 Virtual Page VP 大小固定的块 一般情况下 每个虚拟页的大小默认是4096字节 同样的 物理内存也被分割为物理页 Physical Page PP 也为4096字节 一 mma
  • Nuxt脚手架nuxi初始化失败原因&解决方法

    起因 前几天终于把毕业设计的开题报告整完了 有了一点时间干自己的事 于是就想着学学nuxt3 结果发现跟着官方教程敲的第一行命令就出现了问题 npx nuxi init nuxt3 app 这行代码是nuxt的脚手架 会生成一个最简单的模板
  • 连接器链调用---Spring源码从入门到精通(二十三)

    上篇文章主要介绍了methodInterceptor拦截器吧增强组件返回的过程 获取拦截器链MethodInterceptor Spring源码从入门到精通 二十二 这篇文章主要介绍拦截器链如何触发 一 拦截器链的触发过程 1 首先第一个判
  • 比Mojo慢68000倍,Python性能差的锅该给GIL吗?

    关注并星标腾讯云开发者 每周1 鹅厂工程师带你审判技术 第3期 李志瑞 天使还是魔鬼 聊聊 Python GIL 9 月 7 日 新兴编程语言 Mojo 正式发布 Mojo 的最初设计目标是比 Python 快 35000 倍 近期该团队表
Powered by Hwhale