当前位置: 首页 > news >正文

彻底搞懂nodejs事件循环

nodejs是单线程执行的,同时它又是基于事件驱动的非阻塞IO编程模型。这就使得我们不用等待异步操作结果返回,就可以继续往下执行代码。当异步事件触发之后,就会通知主线程,主线程执行相应事件的回调。

以上是众所周知的内容。今天我们从源码入手,分析一下nodejs的事件循环机制。

nodejs架构

首先,我们先看下nodejs架构,下图所示:

如上图所示,nodejs自上而下分为

  • 用户代码 ( js 代码 )

用户代码即我们编写的应用程序代码、npm包、nodejs内置的js模块等,我们日常工作中的大部分时间都是编写这个层面的代码。

  • binding代码或者三方插件(js 或 C/C++ 代码)

胶水代码,能够让js调用C/C++的代码。可以将其理解为一个桥,桥这头是js,桥那头是C/C++,通过这个桥可以让js调用C/C++。
在nodejs里,胶水代码的主要作用是把nodejs底层实现的C/C++库暴露给js环境。
三方插件是我们自己实现的C/C++库,同时需要我们自己实现胶水代码,将js和C/C++进行桥接。

  • 底层库

nodejs的依赖库,包括大名鼎鼎的V8、libuv。
V8: 我们都知道,是google开发的一套高效javascript运行时,nodejs能够高效执行 js 代码的很大原因主要在它。
libuv:是用C语言实现的一套异步功能库,nodejs高效的异步编程模型很大程度上归功于libuv的实现,而libuv则是我们今天重点要分析的。
还有一些其他的依赖库
http-parser:负责解析http响应
openssl:加解密
c-ares:dns解析
npm:nodejs包管理器

关于nodejs不再过多介绍,大家可以自行查阅学习,接下来我们重点要分析的就是libuv。

libuv 架构

我们知道,nodejs实现异步机制的核心便是libuv,libuv承担着nodejs与文件、网络等异步任务的沟通桥梁,下面这张图让我们对libuv有个大概的印象:

这是libuv官网的一张图,很明显,nodejs的网络I/O、文件I/O、DNS操作、还有一些用户代码都是在 libuv 工作的。
既然谈到了异步,那么我们首先归纳下nodejs里的异步事件:

  • 非I/O:
    • 定时器(setTimeout,setInterval)
    • microtask(promise)
    • process.nextTick
    • setImmediate
    • DNS.lookup
  • I/O:
    • 网络I/O
    • 文件I/O
    • 一些DNS操作

网络I/O

对于网络I/O,各个平台的实现机制不一样,linux 是 epoll 模型,类 unix 是 kquene 、windows 下是高效的 IOCP 完成端口、SunOs 是 event ports,libuv 对这几种网络I/O模型进行了封装。

文件I/O、异步DNS操作

libuv内部还维护着一个默认4个线程的线程池,这些线程负责执行文件I/O操作、DNS操作、用户异步代码。当 js 层传递给 libuv 一个操作任务时,libuv 会把这个任务加到队列中。之后分两种情况:

  • 1、线程池中的线程都被占用的时候,队列中任务就要进行排队等待空闲线程。
  • 2、线程池中有可用线程时,从队列中取出这个任务执行,执行完毕后,线程归还到线程池,等待下个任务。同时以事件的方式通知event-loop,event-loop接收到事件执行该事件注册的回调函数。

当然,如果觉得4个线程不够用,可以在nodejs启动时,设置环境变量UV_THREADPOOL_SIZE来调整,出于系统性能考虑,libuv 规定可设置线程数不能超过128个。

nodejs源码

先简要介绍下nodejs的启动过程:

  • 1、调用platformInit方法 ,初始化 nodejs 的运行环境。
  • 2、调用 performance_node_start 方法,对 nodejs 进行性能统计。
  • 3、openssl设置的判断。
  • 4、调用v8_platform.Initialize,初始化 libuv 线程池。
  • 5、调用 V8::Initialize,初始化 V8 环境。
  • 6、创建一个nodejs运行实例。
  • 7、启动上一步创建好的实例。
  • 8、开始执行js文件,同步代码执行完毕后,进入事件循环。
  • 9、在没有任何可监听的事件时,销毁 nodejs 实例,程序执行完毕。

以上就是 nodejs 执行一个js文件的全过程。接下来着重介绍第八个步骤,事件循环。

我们看几处关键源码:

  • 1、core.c,事件循环运行的核心文件。
int uv_run(uv_loop_t* loop, uv_run_mode mode) {int timeout;int r;int ran_pending;
//判断事件循环是否存活。r = uv__loop_alive(loop);//如果没有存活,更新时间戳if (!r)uv__update_time(loop);
//如果事件循环存活,并且事件循环没有停止。while (r != 0 && loop->stop_flag == 0) {//更新当前时间戳uv__update_time(loop);//执行 timers 队列uv__run_timers(loop);//执行由于上个循环未执行完,并被延迟到这个循环的I/O 回调。ran_pending = uv__run_pending(loop); //内部调用,用户不care,忽略uv__run_idle(loop); //内部调用,用户不care,忽略uv__run_prepare(loop); timeout = 0; if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)//计算距离下一个timer到来的时间差。timeout = uv_backend_timeout(loop);//进入 轮询 阶段,该阶段轮询I/O事件,有则执行,无则阻塞,直到超出timeout的时间。uv__io_poll(loop, timeout);//进入check阶段,主要执行 setImmediate 回调。uv__run_check(loop);//进行close阶段,主要执行 **关闭** 事件uv__run_closing_handles(loop);if (mode == UV_RUN_ONCE) {//更新当前时间戳uv__update_time(loop);//再次执行timers回调。uv__run_timers(loop);}//判断当前事件循环是否存活。r = uv__loop_alive(loop); if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)break;}/* The if statement lets gcc compile it to a conditional store. Avoids   * dirtying a cache line.   */if (loop->stop_flag != 0)loop->stop_flag = 0;return r;
}
  • 2、timers 阶段,源码文件:timers.c
void uv__run_timers(uv_loop_t* loop) {struct heap_node* heap_node;uv_timer_t* handle;for (;;) {//取出定时器堆中超时时间最近的定时器句柄heap_node = heap_min((struct heap*) &loop->timer_heap);if (heap_node == NULL)break;handle = container_of(heap_node, uv_timer_t, heap_node);// 判断最近的一个定时器句柄的超时时间是否大于当前时间,如果大于当前时间,说明还未超时,跳出循环。if (handle->timeout > loop->time)break;// 停止最近的定时器句柄uv_timer_stop(handle);// 判断定时器句柄类型是否是repeat类型,如果是,重新创建一个定时器句柄。uv_timer_again(handle);//执行定时器句柄绑定的回调函数handle->timer_cb(handle);}
}
  • 3、 轮询阶段 源码,源码文件:kquene.c
void uv__io_poll(uv_loop_t* loop, int timeout) {/*一连串的变量初始化*///判断是否有事件发生    if (loop->nfds == 0) {//判断观察者队列是否为空,如果为空,则返回assert(QUEUE_EMPTY(&loop->watcher_queue));return;}nevents = 0;// 观察者队列不为空while (!QUEUE_EMPTY(&loop->watcher_queue)) {/*    取出队列头的观察者对象    取出观察者对象感兴趣的事件并监听。    */....省略一些代码w->events = w->pevents;}assert(timeout >= -1);//如果有超时时间,将当前时间赋给base变量base = loop->time;// 本轮执行监听事件的最大数量count = 48; /* Benchmarks suggest this gives the best throughput. *///进入监听循环for (;; nevents = 0) {// 有超时时间的话,初始化specif (timeout != -1) {spec.tv_sec = timeout / 1000;spec.tv_nsec = (timeout % 1000) * 1000000;}if (pset != NULL)pthread_sigmask(SIG_BLOCK, pset, NULL);// 监听内核事件,当有事件到来时,即返回事件的数量。// timeout 为监听的超时时间,超时时间一到即返回。// 我们知道,timeout是传进来得下一个timers到来的时间差,所以,在timeout时间内,event-loop会一直阻塞在此处,直到超时时间到来或者有内核事件触发。nfds = kevent(loop->backend_fd,events,nevents,events,ARRAY_SIZE(events),timeout == -1 ? NULL : &spec);if (pset != NULL)pthread_sigmask(SIG_UNBLOCK, pset, NULL);/* Update loop->time unconditionally. It's tempting to skip the update when     * timeout == 0 (i.e. non-blocking poll) but there is no guarantee that the     * operating system didn't reschedule our process while in the syscall.     */SAVE_ERRNO(uv__update_time(loop));//如果内核没有监听到可用事件,且本次监听有超时时间,则返回。if (nfds == 0) {assert(timeout != -1);return;}if (nfds == -1) {if (errno != EINTR)abort();if (timeout == 0)return;if (timeout == -1)continue;/* Interrupted by a signal. Update timeout and poll again. */goto update_timeout;}。。。//判断事件循环的观察者队列是否为空assert(loop->watchers != NULL);loop->watchers[loop->nwatchers] = (void*) events;loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds;// 循环处理内核返回的事件,执行事件绑定的回调函数for (i = 0; i < nfds; i++) {。。。。}}

参考 前端进阶面试题详细解答

uv__io_poll阶段源码最长,逻辑最为复杂,可以做个概括,如下:
当js层代码注册的事件回调都没有返回的时候,事件循环会阻塞在poll阶段。看到这里,你可能会想了,会永远阻塞在此处吗?

1、首先呢,在poll阶段执行的时候,会传入一个timeout超时时间,该超时时间就是poll阶段的最大阻塞时间。
2、其次呢,在poll阶段,timeout时间未到的时候,如果有事件返回,就执行该事件注册的回调函数。timeout超时时间到了,则退出poll阶段,执行下一个阶段。

所以,我们不用担心事件循环会永远阻塞在poll阶段。

以上就是事件循环的两个核心阶段。限于篇幅,timers阶段的其他源码和setImmediateprocess.nextTick的涉及到的源码就不罗列了,感兴趣的童鞋可以看下源码。

最后,总结出事件循环的原理如下,以上你可以不care,记住下面的总结就好了。

事件循环原理

  • node 的初始化
    • 初始化 node 环境。
    • 执行输入代码。
    • 执行 process.nextTick 回调。
    • 执行 microtasks。
  • 进入 event-loop
    • 进入 timers 阶段
      • 检查 timer 队列是否有到期的 timer 回调,如果有,将到期的 timer 回调按照 timerId 升序执行。
      • 检查是否有 process.nextTick 任务,如果有,全部执行。
      • 检查是否有microtask,如果有,全部执行。
      • 退出该阶段。
    • 进入IO callbacks阶段。
      • 检查是否有 pending 的 I/O 回调。如果有,执行回调。如果没有,退出该阶段。
      • 检查是否有 process.nextTick 任务,如果有,全部执行。
      • 检查是否有microtask,如果有,全部执行。
      • 退出该阶段。
    • 进入 idle,prepare 阶段:
      • 这两个阶段与我们编程关系不大,暂且按下不表。
    • 进入 poll 阶段
      • 首先检查是否存在尚未完成的回调,如果存在,那么分两种情况。
        • 第一种情况:
          • 如果有可用回调(可用回调包含到期的定时器还有一些IO事件等),执行所有可用回调。
          • 检查是否有 process.nextTick 回调,如果有,全部执行。
          • 检查是否有 microtaks,如果有,全部执行。
          • 退出该阶段。
        • 第二种情况:
          • 如果没有可用回调。
          • 检查是否有 immediate 回调,如果有,退出 poll 阶段。如果没有,阻塞在此阶段,等待新的事件通知。
      • 如果不存在尚未完成的回调,退出poll阶段。
    • 进入 check 阶段。
      • 如果有immediate回调,则执行所有immediate回调。
      • 检查是否有 process.nextTick 回调,如果有,全部执行。
      • 检查是否有 microtaks,如果有,全部执行。
      • 退出 check 阶段
    • 进入 closing 阶段。
      • 如果有immediate回调,则执行所有immediate回调。
      • 检查是否有 process.nextTick 回调,如果有,全部执行。
      • 检查是否有 microtaks,如果有,全部执行。
      • 退出 closing 阶段
    • 检查是否有活跃的 handles(定时器、IO等事件句柄)。
      • 如果有,继续下一轮循环。
      • 如果没有,结束事件循环,退出程序。

细心的童鞋可以发现,在事件循环的每一个子阶段退出之前都会按顺序执行如下过程:

  • 检查是否有 process.nextTick 回调,如果有,全部执行。
  • 检查是否有 microtaks,如果有,全部执行。
  • 退出当前阶段。

记住这个规律哦。

相关文章:

彻底搞懂nodejs事件循环

nodejs是单线程执行的&#xff0c;同时它又是基于事件驱动的非阻塞IO编程模型。这就使得我们不用等待异步操作结果返回&#xff0c;就可以继续往下执行代码。当异步事件触发之后&#xff0c;就会通知主线程&#xff0c;主线程执行相应事件的回调。 以上是众所周知的内容。今天…...

Linux基础命令大全(下)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…...

Matplotlib从入门到精通05-样式色彩秀芳华

Matplotlib从入门到精通05-样式色彩秀芳华总结Matplotlib从入门到精通05-样式色彩秀芳华导入依赖一、matplotlib的绘图样式&#xff08;style&#xff09;1.matplotlib预先定义样式2.用户自定义stylesheet3.设置rcparams二、matplotlib的色彩设置&#xff08;color&#xff09;…...

< CSS小技巧:那些不常用,却很惊艳的CSS属性 >

文章目录&#x1f449; 前言&#x1f449; 一. background-clip: text - 限制背景显示&#xff08;裁剪&#xff09;&#x1f449; 二. user-select - 控制用户能否选中文本&#x1f449; 三. :focus-within 伪类&#x1f449; 四. gap - 网格 / 弹性布局间隔设置&#x1f449;…...

GPT-4 重磅发布,用户直呼:强得离谱

ChatGPT沉寂了一会&#xff0c;OpenAI 的新“核弹”又来了&#xff0c;GPT-4&#xff0c;并且它还非常擅长编码。闲话不提&#xff0c;直捣黄龙。 OpenAI 宣布发布 GPT-4 ChatGPT-4这是 OpenAI 努力扩展深度学习的最新里程碑&#xff0c;GPT-4 是一个大型多模态模型。 据悉&a…...

【JavaSE】知识点总结(3)

目录 一、类定义和使用 1. 类的定义 2. 类的实例化 3. 构造方法 构造方法的重载 二、this关键字 三、 static 修饰属性 四、封装 2. getter与setter 五、继承 1. 继承的语法 2. 子类中访问父类 3. 关于继承原则 4. super关键字 5. super和this 6. protected 关键…...

MySQL基础(三)聚合函数、子查询

目录 聚合函数 AVG/SUM/MAX/MIN COUNT函数 GROUP BY HAVING having和where的区别 SELECT的执行过程 子查询 单行子查询vs多行子查询 单行子查询 多行子查询 关联子查询 EXISTS 与 NOT EXISTS关键字 聚合函数 聚合函数作用于一组数据&#xff0c;并对一组数据返回一个…...

深度学习数据集处理基础内容——xml和json文件详解

文章目录一、xml文件1.1 什么是 XML&#xff1f;1.2XML 和 HTML 之间的差异1.3XML 不会做任何事情1.4通过 XML 您可以发明自己的标签1.5XML 不是对 HTML 的替代1.6XML 无所不在二、json文件基本的JSON结构体类型&#xff08;共享部分&#xff09;三、转COCO数据集3.1 info3.2 l…...

蓝桥杯基础技能训练

51单片机系统浓缩图 1. HC138译码器 用3个输入引脚&#xff0c;实现8个输出引脚&#xff0c;而且这个八个输出引脚中只要一个低电平&#xff0c;所以我们只需要记住真值表就行 #include "reg52.h" sbit HC138_A P2^5; sbit HC138_B P2^6; sbit HC…...

【Kubernetes】第二十八篇 - 实现自动构建部署

一&#xff0c;前言 上一篇&#xff0c;介绍了 Deployment、Service 的创建&#xff0c;完成了前端项目的构建部署&#xff1b; 希望实现&#xff1a;推送代码 -> 自动构建部署-> k8s 滚动更新&#xff1b; 本篇&#xff0c;实现自动构建部署 二&#xff0c;推送触发构…...

蓝桥杯刷题第十天

第一题&#xff1a;裁纸刀问题描述本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。小蓝有一个裁纸刀&#xff0c;每次可以将一张纸沿一条直线裁成两半。小蓝用一张纸打印出两行三列共 6 个二维码&#xff0c;至少使用九次裁出来…...

网络安全缓冲区溢出与僵尸网络答题分析

一、缓冲区溢出攻击 缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量&#xff0c;溢出的数据覆盖在合法数据上。理想的情况是&#xff1a;程序会检查数据长度&#xff0c;而且并不允许输入超过缓冲区长度的字符。但是绝大多数程序都会假设数据长度总是…...

机器学习:逻辑回归模型算法原理(附案例实战)

机器学习&#xff1a;逻辑回归模型算法原理 作者&#xff1a;AOAIYI 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;AOAIYI首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x1f44d;收藏&#x…...

IO流之 File 类和字节流

文章目录一、File 类1. 概述2. 创建功能3. 删除功能4. 判断和获取功能5. 递归策略5.1 递归求阶乘5.2 遍历目录二、字节流1. IO 流概述2. 字节流写数据2.1 三种方式2.2 换行及追加2.3 加异常处理3. 字节流读数据3.1 一次读一个字节3.2 一次读一个字节数组3.3 复制文本文件3.4 复…...

【华为机试真题 Python实现】2023年1、2月高频机试题

文章目录2023年1季度最新机试题机考注意事项1. 建议提前刷题2. 关于考试设备3. 关于语言环境3.1. 编译器信息3.2. ACM 模式使用sys使用input&#xff08;推荐&#xff09;3. 关于题目分值及得分计算方式4. 关于做题流程5. 关于作弊2023年1季度最新机试题 两个专栏现在有200博文…...

【拳打蓝桥杯】最基础的数组你真的掌握了吗?

文章目录一&#xff1a;数组理论基础二&#xff1a;数组这种数据结构的优点和缺点是什么&#xff1f;三&#xff1a;数组是如何实现随机访问的呢&#xff1f;四&#xff1a;低效的“插入”和“删除”原因在哪里&#xff1f;五&#xff1a;实战解题1. 移除元素暴力解法双指针法2…...

断崖式难度的春招,可以get这些点

前言 大家好&#xff0c;我是bigsai&#xff0c;好久不见&#xff0c;甚是想念。 开学就等评审结果&#xff0c;还好擦边过了&#xff0c;上周答辩完整理材料&#xff0c;还好都过了(终于可以顺利毕业了)&#xff0c;然后后面就是一直安享学生时代的晚年。 最近金三银四黄金…...

一年经验年初被裁面试1月有余无果,还遭前阿里面试官狂问八股,人麻了

最近接到一粉丝投稿&#xff1a;年初被裁员&#xff0c;在家躺平了6个月&#xff0c;然后想着学习下再去面试&#xff0c;现在面试了1个月有余&#xff0c;无果&#xff0c;天天打游戏到半夜&#xff0c;根本无法静下心来学习。下面是他这些天面试经常会被问到的一些问题&#…...

我从功能测试到python接口自动化测试涨到22k,谁知道我经历了什么......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 常见的接口&#xf…...

SDG,ADAM,LookAhead,Lion等优化器的对比介绍

本文将介绍了最先进的深度学习优化方法&#xff0c;帮助神经网络训练得更快&#xff0c;表现得更好。有很多个不同形式的优化器&#xff0c;这里我们只找最基础、最常用、最有效和最新的来介绍。 优化器 首先&#xff0c;让我们定义优化。当我们训练我们的模型以使其表现更好…...

【项目实现典型案例】12.数据库数据类型不一致导致查询慢

目录一&#xff1a;背景介绍二&#xff1a;索引失效复现四&#xff1a;索引实现的六种情况1、类型转换&#xff0c;函数2、ISNULL3、通配符开头4、范围查询5、组合索引&#xff0c;不符合最左匹配原则6、WHERE子句中的OR四&#xff1a;总结一&#xff1a;背景介绍 MySql数据库…...

【大数据开发】报错汇总

目录 Hadoop Attempting to operate on hdfs namenode as root jps后没有namenode Hive Exception in thread "main" java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;Ljava/lang/Object;)V Caused by:o…...

HTTPS的加密原理(工作机制)

现在很多网站使用的都是HTTPS协议,比如CSDN他们为什么要使用HTTPS协议而不是继续使用HTTP协议呢?以及HTTPS都做了些什么?HTTP协议与HTTPS有哪些区别? 下面我来 讲解这些问题?(篇幅可能有些长,请求耐心观看,我以0基础的角度去讲解这些东西, 如果你有一定的基础前面的跳过就好…...

Git仓库迁移

背景 由于公司原来的gitee地址需要改完新的gitlab仓库&#xff0c;大量的服务模块已再本地进行开发&#xff0c;且存在大量分支进行维护&#xff0c;迁移要求历史提交记录也得同步&#xff0c;需要简单快捷一并完成各服务已经分支迁移。 一、在新的目标git中创建新代码仓 新…...

用CHATGPT生成C++面试题及答案

以下是C的面试题及其答案&#xff1a; 什么是C&#xff1f;C与C语言有什么区别&#xff1f; C是一种高级编程语言&#xff0c;是对C语言的扩展。C具有更强大的面向对象编程能力&#xff0c;支持类、继承、多态等特性。 什么是面向对象编程&#xff1f; 面向对象编程是一种编程…...

二进制,八进制,十进制,十六进制的相互转换【简单易懂】(含代码模板)

目录 二进制转十进制 十进制原理&#xff1a; 二进制转十进制计算&#xff1a; 八、十六进制转十进制 八、十六进制转十进制计算&#xff1a; 十进制转其他进制 十进制转二进制&#xff1a; 十进制转八进制&#xff1a; 十进制转十六进制&#xff1a; 不同进制之间的相互转…...

Redis技术详解

Redis技术详解 Redis是一种支持key-value等多种数据结构的存储系统。可用于缓存&#xff0c;事件发布或订阅&#xff0c;高速队列等场景。支持网络&#xff0c;提供字符串&#xff0c;哈希&#xff0c;列表&#xff0c;队列&#xff0c;集合结构直接存取&#xff0c;基于内存&…...

解决mybatis-plus updateById方法不能set null

原因 因为 MyBatis-Plus 自带的更新方法&#xff0c;都有对对象空值进行判空。只有不为空的字段才会进行数据更新 所以像updateById等方法&#xff0c;在更新时会自动忽略为null的字段&#xff0c;只更新非null字段值 但在某些情况下&#xff0c;我们的需求就是将数据库中的值…...

Linux的mysql 数据库及开发包安装

注意&#xff1a;以下操作都以 root 用户进行操作 直接按照下列步骤在命令行输入即可 下载 1&#xff1a; sudo yum install -y mariadb 2&#xff1a; sudo yum install -y mariadb-server 3&#xff1a; sudo yum install -y mariadb-devel 接下来配置文件&#xff1a;在相应…...

π-Day快乐:Python可视化π

π-Day快乐&#xff1a;Python可视化π 今天是3.14&#xff0c;正好是圆周率 π\piπ 的前3位&#xff0c;因此数学界将这一天定为π\bold{\pi}π day。 π\piπ 可能是最著名的无理数了&#xff0c;人类对 π\piπ 的研究从未停止。目前人类借助计算机已经计算到 π\piπ 小数…...

创意设计字体/seo团队

&#x1f352; 作者简介&#xff1a;大学机械本科&#xff0c;野生程序猿&#xff0c;学过C语言&#xff0c;玩过前端&#xff0c;还鼓捣过嵌入式&#xff0c;设计也会一点点&#xff0c;不过如今痴迷于网络爬虫&#xff0c;因此现深耕Python、数据库、seienium、JS逆向、安卓逆…...

公司里面有人员增减要去哪个网站做登记/手机怎么创建网站

linux使用串口说明发布时间:2008-11-18 21:05:49来源:红联作者:emex我是做系统集成的工程师&#xff0c;所以在日常的工作中会经常要用notebook调试ROUTER 或者SWITCH&#xff0c;然而当操作系统换成了linux后就傻眼了&#xff0c;在网上也参考了一些资料&#xff0c;结合自己的…...

杭州电子网站建设方案/百度浏览器打开

基本数据类型python的基本数据类型如下:1. int > 整数. 主要用来进行数学运算2. str > 字符串, 可以保存少量数据并进行相应的操作3. bool>判断真假, True, False4. list> 存储大量数据.用[ ]表示5. tuple> 元组, 不可以发生改变 用( )表示6. dict> 字典, 保…...

wordpress带会员vip主题/全国各城市感染高峰进度查询

指针是 C 语言的灵魂&#xff0c;现在对于指针的掌握肯定是不透彻的&#xff0c;然学习是一个迭代的过程&#xff0c;姑且写出目前自己的理解。指针的概念123456789101112131415161718192021#include #include #include int main(){int a 10;int *p NULL;p (int *)malloc(si…...

wordpress 男扮女/东莞市网站seo内容优化

微信小程序实现路径导航—使用搜索组件Searchbar 腾讯地图sdk一、搜索组件Searchbar二、腾讯地图sdk三、开发准备四、敲代码五、最终效果一、搜索组件Searchbar Searchbar是微信小程序WeUI组件库中的&#xff0c;在【微信官方文档小程序】的【扩展能力】中有详细的介绍哦&…...

日本做a视频网站/微信引流被加软件

while read -r line; do 表示从标准输入或文件中逐行读取数据&#xff0c;并在每次循环中将当前行的内容赋值给变量 line。每个循环中&#xff0c;都会执行后面跟随的命令&#xff0c;直到读到最后一行为止。例如&#xff1a; while read -r line; doecho "$line" do…...