比Mojo慢68000倍,Python性能差的锅该给GIL吗?
# 关注并星标腾讯云开发者
# 每周1 | 鹅厂工程师带你审判技术
# 第3期 | 李志瑞:天使还是魔鬼?聊聊 Python GIL
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 的局限性。
要理解 GIL 对 Python 的影响,我们首先要先明白 GIL 到底是什么以及它是如何工作的。
简单来说,GIL 就是一个 Python 虚拟机中的全局锁,它使得同一时刻只有一个线程能够获得 Python 虚拟机的控制权,防止多个线程同时执行 Python 字节码。在 Python 中,每个线程在执行 Python 字节码的时候都需要持有 GIL,这意味着,多个线程的 Python 字节码解释事实上会被 GIL 强制变为串行执行。那么,这个切换过程是如何发生的呢?事实上,GIL 的实现也随着 Python 的发展发生过明显的变化。在 Python 3.2 之前,Python 虚拟机主要是基于 tick 数来控制 GIL 切换,默认情况下,一个线程在执行 100 条字节码后就会释放 GIL,其他线程就可以去抢占 GIL,如下图所示:
除了 tick 之外,一些阻塞操作,例如 send、recv、sleep 也会触发 GIL 的主动释放,例如 timemodule.c 中的 pysleep 代码:
static int pysleep(_PyTime_t timeout) {...int ret;Py_BEGIN_ALLOW_THREADSret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &timeout_abs, NULL);err = ret;Py_END_ALLOW_THREADS...
}
这里将实际的 sleep 调用使用 Py_BEGIN_ALLOW_THREADS 和 Py_END_ALLOW_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 已经被释放,这既避免了等待中的线程频繁去尝试抢锁,也避免了该线程重复获得锁引发的其他线程饥饿问题:
我们可以很清晰地从 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 是否被设为 1if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request)) {// 释放 GILdrop_gil(ceval, ceval2, tstate);// 此时其他线程就可以开始执行了// 重新获取 GILtake_gil(tstate);}...
}
默认情况下,Python 每 5ms 会在线程间切换一次,我们可以通过 getswitchinterval 来获取当前的切换间隔时间,通过 setswitchinterval 来设置间隔时间(单位为秒):
>>> import sys
>>> sys.getswitchinterval()
0.005
>>> sys.setswitchinterval(1)
>>> sys.getswitchinterval()
1.0
了解 GIL 是如何工作的之后,我们来看一下 GIL 如何影响我们现有的程序实现方式。
对于 Python 程序来说,既然 GIL 使单个虚拟机无法并行执行 Python 程序,那么在需要充分利用多核 CPU 的场景中,最常见的做法就是启动多个 Python 进程,这也是 Python 服务器的常见实现方式,例如:
from multiprocessing import Process, Pipedef 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 为什么当初要这样设计呢?
当我们考虑一个技术选择是否合理的时候,不能只以当下的目光去审视过去,而需要回到当时的场景中去考虑问题。从 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 的发展可谓是功不可没。
当然,当时决策的历史局限性会在时间往前推进的过程中越发明显,尤其是到了摩尔定律终结的时候:
我们知道,CPU 的单核计算能力并没有如摩尔定律预言的那样无限提高,CPU 的性能逐渐显露出瓶颈,除了针对特殊用途定制特定芯片之外,加强计算能力的渠道就逐步从依赖提高单核计算能力的提升转变为将芯片做小并增加核心数量。硬件的风向转变影响了软件开发,开发者们希望 Python 也能提供充分利用多核硬件来实现并行计算的能力。因此 GIL 带来的约束就使它变得更加臭名昭著了,它也就从促进 Python 流行的天使慢慢变为限制 Python 发展的魔鬼。
话虽如此,但 GIL 也并不是想去掉就能去掉。Guido van Rossum 曾在博客《It isn't Easy to Remove the GIL》中正面回应过为什么不从 CPython 中去掉 GIL,这里主要提到了两个问题:
|
另外,在 Python 社区中,CPython 的维护者对于去掉 GIL 的提案还有一些明确的约束:
|
可以看出,移除 GIL 的提案其实非常难以获得通过。一件显而易见的事情是,针对多线程的架构在单线程下非常难以达到和针对单线程的架构同样的执行效率,因此这对于实现者来说本身就具有非常大的挑战。一个典型例子是 CPython 的 GC 实现,目前 CPython GC 的方案是以引用计数为主,辅以标记清除来处理循环引用的情况。每个 Python 对象在 CPython 虚拟机内大致表示为这样的形式:
struct _object {_PyObject_HEAD_EXTRAPy_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,以便让有需要的用户逐步迁移。这当然是一个稳健的做法,不过这个过渡时间有多久,就还真是一个未知数了,需要实践之后才能知道结果。
Python GIL 并不像很多人想象的那样是一个愚蠢的决策,在当时的时代背景下,它很大程度促进了 Python 的发展和生态的繁荣。只是随着时代的变迁,完成历史使命的它也到了逐渐退出历史舞台的时候了。虽然这个过程无法一蹴而就,但目前渐进的改变更能保障兼容性,让用户有一个平滑过渡的机会。然而编程语言届从来都没有风平浪静的一天,想要挑战 Python 地位的竞争者不断出现,例如前文提到的 Mojo 就号称兼容 Python 并巨幅提升性能,至于 Python 的未来会如何,还请拭目以待。
-End-
原创作者|李志瑞
你是否支持 Python 中移除 GIL,为什么?欢迎再评论区分享。我们将选取1则最有意义的评论,送出腾讯云开发者-便携通勤袋1个(见下图)。9月18日中午12点开奖。
📢📢欢迎加入腾讯云开发者社群,社群专享券、大咖交流圈、第一手活动通知、限量鹅厂周边等你来~
(长按图片立即扫码)
关注并星标腾讯云开发者
每周1看鹅厂程序员聊热门技术
相关文章:
比Mojo慢68000倍,Python性能差的锅该给GIL吗?
# 关注并星标腾讯云开发者 # 每周1 | 鹅厂工程师带你审判技术 # 第3期 | 李志瑞:天使还是魔鬼?聊聊 Python GIL 9 月 7 日,新兴编程语言 Mojo 正式发布。Mojo 的最初设计目标是比 Python 快 35000 倍,近期该团队表示,因…...
CSS读书笔记
——————————————精华部分—————————————— 1、选择器 (1)基本选择器: 标签选择器 body{} 类选择器 class .class名称{} ID选择器 id #id名称{} 优先级:ID选择器 > 类选择器 > 标签选择器 &am…...
Qt使用QSqlDatabase remoeDatabase()连接提示仍在使用解决方案
问题描述 调用QSqlDatabase的removeDatabase函数的时候,出现了如下错误 QSqlDatabasePrivate::removeDatabase: connection 05465461654654 is still in use, all queries will cease to work官方示例 [static] void QSqlDatabase::removeDatabase(const QString &…...
管易云与金蝶云星空对接集成仓库查询打通仓库新增
管易云与金蝶云星空对接集成仓库查询打通仓库新增 接通系统:管易云 管易云是金蝶旗下专注提供电商企业管理软件服务的子品牌,先后开发了C-ERP、EC-OMS、EC-WMS、E店管家、BBC、B2B、B2C商城网站建设等产品和服务,涵盖电商业务全流程。 对接目…...
ubuntu 安装 Mongodb 4.0、4.2、4.4
1. 安装 # 配置apt Repository mongodb 4.0, 4.2, 4.4 $ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 68818c72e52529d4 #4.0 $ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 4B7C549A058F8B6B #4.2 $ …...
详解Hugging Face Transformers的TrainingArguments
前言: TrainingArguments是Hugging Face Transformers库中用于训练模型时需要用到的一组参数,用于控制训练的流程和效果。 使用示例: from transformers import Trainer,TrainingArguments training_args TrainingArguments(output_dir&q…...
【LeetCode-中等题】17. 电话号码的字母组合
文章目录 题目方法一:递归回溯 题目 方法一:递归回溯 参考讲解:还得用回溯算法!| LeetCode:17.电话号码的字母组合 首先可以画出树图: 先将数字对应的字符集合 加入到一个map集合 这里需要一个index来控…...
读高性能MySQL(第4版)笔记06_优化数据类型(上)
1. 良好的逻辑设计和物理设计是高性能的基石 1.1. 反范式的schema可以加速某些类型的查询,但同时可能减慢其他类型的查询 1.2. 添加计数器和汇总表是一个优化查询的好方法,但它们的维护成本可能很 1.3. 将修改schema作为一个常见事件来规划 2. 让事情…...
mac如何创建mysql数据库
使用mac创建mysql数据库十分简单,我们只需要按照以下步骤即可完成。 首先,我们需要安装mysql,我们可以通过官网下载对应的安装包,或者通过Homebrew进行安装。 接下来,我们需要启动mysql服务,在终端中输入以…...
Ceph入门到精通-centos8 install brctl
在centos7上是可以直接yum安装bridge-utils的,但是centos8不行 经过分析 brctl有提供centos的rpm包,里面只有一个二进制脚本,所以直接下载安装即可 rpm -ivh http://mirror.centos.org/centos/7/os/x86_64/Packages/bridge-utils-1.5-9.el7…...
sqli第一关
1.在下使用火狐访问sqlilabs靶场并使用burpsuite代理火狐。左为sqlilabs第一关,右为burpsuite。 2.输入?id1 and 11 与?id1 and 12试试 可以看出没有变化哈,明显我们输入的语句被过滤了。在?id1后面尝试各种字符,发现单引号 包…...
入行IC | 新人入行IC选择哪个岗位更好?
很多同学入行不知道怎么选择岗位。IC的岗位一般有设计、验证、后端、封装、测试、FPGA等等。但是具体到每个人身上,就要在开始的时候确定下你要找的职位,可以有两个或三个,但是要分出主次,主次不分会让你纠结整个找工作的过程。 …...
时间旅行的Bug 奇怪的输入Bug
故事一:时间旅行的Bug 在一个普通的工作日,程序员小明正在开发一个时间旅行的应用程序。这个应用程序可以让用户选择一个特定的日期和时间,然后将用户的意识传送到过去或未来的那个时刻。小明对这个项目非常兴奋,他认为这将是一个…...
解决nbsp;不生效的问题
代码块 {{title}} title:附 \xa0\xa0\xa0件,//或者 <span v-html"title"></span> title:附 件:,效果图...
【Lidar】Cloud Compare介绍安装包
CloudCompare是一款基于GPL开源协议的3D点云处理软件,最初被设计用来对稠密的三维点云进行直接比较。它依赖于一种特定的八叉树结构,在进行点云对比这类任务时具有出色的性能。在2005年后,CloudCompare实现了点云和三角形网格之间的比较。 Cl…...
Java中的Maven是什么?
Maven是一个开源的项目管理和构建工具,用于Java项目的构建、依赖管理和项目信息管理。它提供了一种标准的项目结构、规范的构建过程和丰富的插件生态系统,简化了项目的管理和构建过程。 Maven基于项目对象模型(Project Object Model…...
计算机操作系统
计算机操作系统 1.进程管理 1.1 基础概念 进程&线程 进程是操作系统资源分配的基本单位。一个进程运行时,会获取必要的CPU、内存地址空间,以及运行时必要的IO设备。 线程则是执行调度的最小单位。一个进程会由一个线程或者多个线程执行调度任务。…...
海学会读《乡村振兴战略下传统村落文化旅游设计》2023年度许少辉八一新书
海学会读《乡村振兴战略下传统村落文化旅游设计》2023年度许少辉八一新书...
tkinter树形图组件
文章目录 初步回调函数绑定滚动条 初步 Treeview是ttk中的树形表组件,功能十分强大,非常适用于系统路径的表达。为了知道属性图到底是什么,下面先做个最简单的树形图 其代码如下 import tkinter as tk from tkinter import ttkdct {"…...
多线程的创建
一、基本概念 1 cpu CPU的中文名称是中央处理器,是进行逻辑运算用的,主要由运算器、控制器、寄存器三部分组成,从字面意思看就是运算就是起着运算的作用,控制器就是负责发出cpu每条指令所需要的信息,寄存器就是保存运…...
【django】APPEND_SLASH 路由末尾的斜杠问题
url路由末尾是否加斜杠的规范 加斜杠:表示是目录不加斜杠: 表示是文件 在django中的setting中,默认APPEND_SLASH True, 即当请求的路由末尾没有加斜杠, 如果尝试加上斜杠后,能在后端路由里匹配到,则会自…...
iOS16.0:屏幕旋转
此文写于2022年08月03日,距离iOS16正式版推出还有一个多月的时间,iOS16 beta版本有很多API的修改,今天讨论的是屏幕旋转,基于Xcode 14.0 beta4。 之前的屏幕旋转会报错: [Orientation] BUG IN CLIENT OF UIKIT: Settin…...
Carla学习笔记(二)服务器跑carla,本地运行carla-ros-bridge并用rviz显示
一、服务器跑carla 详见Carla学习笔记(一)服务器跑carla本地显示窗口_Zero_979的博客-CSDN博客 只需要启动服务器端就行: ./CarlaUE4.sh -carla-rpc-port2000 -RenderOffScreen -graphicsadaper1 二、本地下载 carla-ros-bridge 官方库&…...
数学建模--退火算法求解最值的Python实现
目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 """ 1.设定退火算法的基础参数 2.设定需要优化的函数,求解该函数的最小值/最大值 3.进行退火过程,随机产生退火解并且纠正,直到冷却 4.绘制可视化图片进行了解退火整体过程 &…...
地理地形sdk:Tatuk GIS Developer Kernel for .NET Crack
Tatuk GIS Developer Kernel for .NET 是一个变体,它是受控代码和 .NET GIS SDK,用于为用户 Windows 操作系统创建专业 GIS 软件的过程。它被认为是一个完全针对Win Forms 的.NET CIL,WPF 框架是针对C# 以及VB.NET、VC、Oxy 以及最终与.NET 的…...
Day_81-87 CNN卷积神经网络
目录 一. CNN卷积神经网络与传统神经网络的不同 1. 模型图 2. 参数分布情况 3. 卷积神经网络和传统神经网络的层次结构 4. 传统神经网络的缺点: 二. CNN的基本操作 1. 卷积 2. 池化 三. CNN实现过程 1. 算法流程图 2. 输入层 3. 卷积层 4. 激活层 5. 池化层 6. 全连…...
关于mybatisplus报错:Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplat的问题
可能是mybatisplus版本不兼容的问题,我之前用的3.4.0,springboot版本是3.1.3,maven版本是3.8.8,运行的时候报了这个错。现在修改了mybatisplus的版本,如下图: 这样就不报错了。 大家可以在这里找合适的my…...
Spring AOP基础动态代理基于JDK动态代理实现
目录 1. 预备知识-动态代理 1.1 什么是动态代理 1.2 动态代理的优势 1.3 基于JDK动态代理实现 2. AOP 2.1 基本概念 2.2 AOP带来的好处 3. Spring AOP 3.1 前置通知 3.2 后置通知 3.3 环绕通知 3.4 异常通知 3.5 适配器 1. 预备知识-动态代理 1.1 什么是动态代理…...
第一章 计算机系统概述 五、中断和异常、系统调用
目录 一、中断的作用 二、中断的类型 1、内中断(异常) 2、外中断 三、中断机制的基本原理 四、系统调用 1、定义: 2、与库函数的区别 3、按功能分类 4、作用 一、中断的作用 1、“中断”是让操作系统内核夺回CPU使用权的唯一途径 …...
【C语言】文件操作(上)
一.什么是文件 文件是磁盘上的文件,文件中存放的数据不随程序的退出而销毁. 二.文件的打开与关闭 1.文件指针 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等&…...
洛阳专业网站设计开发制作建站公司/推广普通话手抄报简单漂亮
题图:https://unsplash.com/nielsenramon在OO(面向对象)编程中,类中的方法有多种形式:实例方法、静态方法、类方法、甚至还可以有抽象方法,本文来说说实例方法在Python中是如何工作的,后面再来谈其他方法。先来定义一个…...
17网站一起做网店官网/我想创建一个网络平台
array_unique() //移除重复数据array_unshift(); //在数组的头部追加数组元素array_shift() //在数组的尾部追加数组元素if(!is_null(array_shift($array1))) //删除成功array_pop() //删除最后一个元素,移除失败返回NULLarray_push() //在数组尾部追加元素arra…...
网站建设支出/互联网营销案例
代理模式 代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能. 这就符合了设计模式的开闭原则,即在对既有代码不改动的情况下进行功…...
织梦网站搬家/搜索推广代运营
了解线程之前得先了解下什么是进程, 进程:操作系统 调度程序 动态 线程:在进程内的多条执行路径 就像之前的一个main主进程在跑,后来将一条路分成了多份,多个一起跑 一个进程可以同时拥有多个进程 线程和进程的详…...
logo设计说明模板/重庆网站seo诊断
题目名称:蝈蝈式的记分 内容描述: 蝈蝈小朋友刚刚学会了 0-9 这十个数字 , 也跟爸爸妈妈来参加百度每周进行的羽毛球活动。但是他还没有球拍高,于是大人们叫他记录分数。聪明的蝈蝈发现只要记录连续得分的情况就可以了,比如用…...
一学一做征文网站/广东seo教程
Lvs Dr模式的模拟测试 1.用ifconfig配置IP,Lvs的ip为200.168.10.1, realserver1的ip为200.168.10.2,realserver2的ip为200.168.10.3,虚拟IP 为200.168.10.10。 2.Lvs server配置: Shell>ifconfig eth0:0 200.168.10.10 netmask…...