React 第三十六章 Scheduler 任务调度

Scheduler 用于在 React 应用中进行任务调度。它可以帮助开发人员在处理复杂的任务和操作时更好地管理和优化性能。

关于 Scheduler 在React 如何渲染的可以参考 React 第三十四章 React 渲染流程

下面我们根据流程图先简单的了解 Scheduler 的调度过程

image-20221230103504711

Scheduler 维护两个队列,分别存放普通任务和延时任务

  • taskQueue::普通任务
  • timerQueue:延时任务

Scheduler 接收的是普通任务时,会加入普通任务队列,然后执行 requestHostCallback

schedulePerformWorkUntilDeadline 根据环境创建宏任务(主要创建的就是 MessageChannel),然后执行 performWorkUntilDeadline。该方法实际上主要就是在调用 scheduledHostCallback(flushWork),调用之后,返回一个布尔值,根据这个布尔值来判断是否还有剩余的任务,如果还有,就是用 messageChannel 进行一个宏任务的包装,放入到任务队列里面。flushWork 主要是调用 wookLoop。workLoop 在当前贞中只要还有时间,就会不停的执行任务

Scheduler 接收的是延时任务时,会加入延时队列,然后执行 requestHostTimout(主要是设置setTimeout),然后执行 handleTimeout,将 到时间的延时任务加入到 普通任务队列,然后执行 requestHostTimout。接下来的操作就和普通任务队列接下来的操作一致。

下面我们来看源码的具体实现。Scheduler 的核心源码位于 packages/scheduler/src/forks/Scheduler.js。 unstable_scheduleCallback就是我们要看到的 Scheduler

schedule调度普通任务

scheduleCallback 该函数的主要目的就是用调度任务,该方法的分析如下:

let getCurrentTime = () => performance.now();// 有两个队列分别存储普通任务和延时任务
// 里面采用了一种叫做小顶堆的算法,保证每次从队列里面取出来的都是优先级最高(时间即将过期)
var taskQueue = []; // 存放普通任务
var timerQueue = []; // 存放延时任务var maxSigned31BitInt = 1073741823;// Timeout 对应的值
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;/**** @param {*} priorityLevel 优先级等级* @param {*} callback 具体要做的任务* @param {*} options { delay: number } 这是一个对象,该对象有 delay 属性,表示要延迟的时间* @returns*/
function unstable_scheduleCallback(priorityLevel, callback, options) {// 获取当前的时间var currentTime = getCurrentTime();var startTime;// 整个这个 if.. else 就是在设置起始时间,如果有延时,起始时间需要添加上这个延时if (typeof options === "object" && options !== null) {var delay = options.delay;// 如果设置了延时时间,那么 startTime 就为当前时间 + 延时时间if (typeof delay === "number" && delay > 0) {startTime = currentTime + delay;} else {startTime = currentTime;}} else {startTime = currentTime;}var timeout;// 根据传入的优先级等级来设置不同的 timeoutswitch (priorityLevel) {case ImmediatePriority:timeout = IMMEDIATE_PRIORITY_TIMEOUT;break;case UserBlockingPriority:timeout = USER_BLOCKING_PRIORITY_TIMEOUT;break;case IdlePriority:timeout = IDLE_PRIORITY_TIMEOUT;break;case LowPriority:timeout = LOW_PRIORITY_TIMEOUT;break;case NormalPriority:default:timeout = NORMAL_PRIORITY_TIMEOUT;break;}// 接下来就计算出过期时间// 计算出来的时间有些比当前时间要早,绝大部分比当前的时间要晚一些var expirationTime = startTime + timeout;// 创建一个新的任务var newTask = {id: taskIdCounter++, // 任务 idcallback, // 该任务具体要做的事情priorityLevel, // 任务的优先级别startTime, // 任务开始时间expirationTime, // 任务的过期时间sortIndex: -1, // 用于后面在小顶堆(这是一种算法,可以始终从任务队列中拿出最优先的任务)进行排序的索引};if (enableProfiling) {newTask.isQueued = false;}if (startTime > currentTime) {// This is a delayed task.// 说明这是一个延时任务newTask.sortIndex = startTime;// 将该任务推入到 timerQueue 的任务队列中push(timerQueue, newTask);if (peek(taskQueue) === null && newTask === peek(timerQueue)) {// 进入此 if,说明 taskQueue 里面的任务已经执行完毕了// 并且从 timerQueue 里面取出一个最新的任务又是当前任务// All tasks are delayed, and this is the task with the earliest delay.// 下面的 if.. else 就是一个开关if (isHostTimeoutScheduled) {// Cancel an existing timeout.cancelHostTimeout();} else {isHostTimeoutScheduled = true;}// Schedule a timeout.// 如果是延时任务,调用 requestHostTimeout 进行任务的调度requestHostTimeout(handleTimeout, startTime - currentTime);}} else {// 说明不是延时任务newTask.sortIndex = expirationTime; // 设置了 sortIndex 后,可以在任务队列里面进行一个排序// 推入到 taskQueue 任务队列push(taskQueue, newTask);if (enableProfiling) {markTaskStart(newTask, currentTime);newTask.isQueued = true;}// Schedule a host callback, if needed. If we're already performing work,// wait until the next time we yield.// 最终调用 requestHostCallback 进行任务的调度if (!isHostCallbackScheduled && !isPerformingWork) {isHostCallbackScheduled = true;requestHostCallback(flushWork);}}// 向外部返回任务return newTask;
}

该方法主要注意以下几个关键点:

  • 关于任务队列有两个,一个 taskQueue,另一个是 timerQueue,taskQueue 存放普通任务,timerQueue 存放延时任务,任务队列内部用到了小顶堆的算法,保证始终放进去(push)的任务能够进行正常的排序,回头通过 peek 取出任务时,始终取出的是时间优先级最高的那个任务
  • 根据传入的不同的 priorityLevel,会进行不同的 timeout 的设置,任务的 timeout 时间也就不一样了,有的比当前时间还要小,这个代表立即需要执行的,绝大部分的时间比当前时间大。

image-20221229145930771

  • 不同的任务,最终调用的函数不一样
    • 普通任务:requestHostCallback(flushWork)
    • 延时任务:requestHostTimeout(handleTimeout, startTime - currentTime);

requestHostCallback 和 schedulePerformWorkUntilDeadline

/*** * @param {*} callback 是在调用的时候传入的 flushWork* requestHostCallback 这个函数没有做什么事情,主要就是调用 schedulePerformWorkUntilDeadline*/
function requestHostCallback(callback) {scheduledHostCallback = callback;// scheduledHostCallback ---> flushWorkif (!isMessageLoopRunning) {isMessageLoopRunning = true;schedulePerformWorkUntilDeadline(); // 实例化 MessageChannel 进行后面的调度}
}let schedulePerformWorkUntilDeadline; // undefined
if (typeof localSetImmediate === 'function') {// Node.js and old IE.// https://github.com/facebook/react/issues/20756schedulePerformWorkUntilDeadline = () => {localSetImmediate(performWorkUntilDeadline);};
} else if (typeof MessageChannel !== 'undefined') {// 大多数情况下,使用的是 MessageChannelconst channel = new MessageChannel();const port = channel.port2;channel.port1.onmessage = performWorkUntilDeadline;schedulePerformWorkUntilDeadline = () => {port.postMessage(null);};
} else {// setTimeout 进行兜底schedulePerformWorkUntilDeadline = () => {localSetTimeout(performWorkUntilDeadline, 0);};
}
  • requestHostCallback 主要就是调用了 schedulePerformWorkUntilDeadline
  • schedulePerformWorkUntilDeadline 一开始是 undefiend,根据不同的环境选择不同的生成宏任务的方式

performWorkUntilDeadline

let startTime = -1;
const performWorkUntilDeadline = () => {// scheduledHostCallback ---> flushWorkif (scheduledHostCallback !== null) {// 获取当前的时间const currentTime = getCurrentTime();// Keep track of the start time so we can measure how long the main thread// has been blocked.// 这里的 startTime 并非 unstable_scheduleCallback 方法里面的 startTime// 而是一个全局变量,默认值为 -1// 用来测量任务的执行时间,从而能够知道主线程被阻塞了多久startTime = currentTime;const hasTimeRemaining = true; // 默认还有剩余时间// If a scheduler task throws, exit the current browser task so the// error can be observed.//// Intentionally not using a try-catch, since that makes some debugging// techniques harder. Instead, if `scheduledHostCallback` errors, then// `hasMoreWork` will remain true, and we'll continue the work loop.let hasMoreWork = true; // 默认还有需要做的任务try {// scheduledHostCallback ---> flushWork(true, 开始时间): boolean// 如果是 true,代表工作没做完// false 代表没有任务了hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);} finally {if (hasMoreWork) {// If there's more work, schedule the next message event at the end// of the preceding one.// 那么就使用 messageChannel 进行一个 message 事件的调度,就将任务放入到任务队列里面schedulePerformWorkUntilDeadline();} else {// 说明任务做完了isMessageLoopRunning = false;scheduledHostCallback = null; // scheduledHostCallback 之前为 flushWork,设置为 null}}} else {isMessageLoopRunning = false;}// Yielding to the browser will give it a chance to paint, so we can// reset this.needsPaint = false;
};
  • 该方法实际上主要就是在调用 scheduledHostCallback(flushWork),调用之后,返回一个布尔值,根据这个布尔值来判断是否还有剩余的任务,如果还有,就是用 messageChannel 进行一个宏任务的包装,放入到任务队列里面

flushWork 和 workLoop

/**** @param {*} hasTimeRemaining 是否有剩余的时间,一开始是 true* @param {*} initialTime 做这一个任务时开始执行的时间* @returns*/
function flushWork(hasTimeRemaining, initialTime) {// ...try {if (enableProfiling) {try {// 核心实际上是这一句,调用 workLoopreturn workLoop(hasTimeRemaining, initialTime);} catch (error) {// ...}} else {// 核心实际上是这一句,调用 workLoopreturn workLoop(hasTimeRemaining, initialTime);}} finally {// ...}
}/**** @param {*} hasTimeRemaining 是否有剩余的时间,一开始是 true* @param {*} initialTime 做这一个任务时开始执行的时间* @returns*/
function workLoop(hasTimeRemaining, initialTime) {let currentTime = initialTime;// 该方法实际上是用来遍历 timerQueue,判断是否有已经到期了的任务// 如果有,将这个任务放入到 taskQueueadvanceTimers(currentTime);// 从 taskQueue 里面取一个任务出来currentTask = peek(taskQueue);while (currentTask !== null &&!(enableSchedulerDebugging && isSchedulerPaused)) {if (currentTask.expirationTime > currentTime &&(!hasTimeRemaining || shouldYieldToHost())) {// This currentTask hasn't expired, and we've reached the deadline.// currentTask.expirationTime > currentTime 表示任务还没有过期// hasTimeRemaining 代表是否有剩余时间// shouldYieldToHost 任务是否应该暂停,归还主线程// 那么我们就跳出 whilebreak;}// 没有进入到上面的 if,说明这个任务到过期时间,并且有剩余时间来执行,没有到达需要浏览器渲染的时候// 那我们就执行该任务即可const callback = currentTask.callback; // 拿到这个任务if (typeof callback === "function") {// 说明当前的任务是一个函数,我们执行该任务currentTask.callback = null;currentPriorityLevel = currentTask.priorityLevel;const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;if (enableProfiling) {markTaskRun(currentTask, currentTime);}// 任务的执行实际上就是在这一句const continuationCallback = callback(didUserCallbackTimeout);currentTime = getCurrentTime();if (typeof continuationCallback === "function") {// If a continuation is returned, immediately yield to the main thread// regardless of how much time is left in the current time slice.// $FlowFixMe[incompatible-use] found when upgrading FlowcurrentTask.callback = continuationCallback;if (enableProfiling) {// $FlowFixMe[incompatible-call] found when upgrading FlowmarkTaskYield(currentTask, currentTime);}advanceTimers(currentTime);return true;} else {if (enableProfiling) {// $FlowFixMe[incompatible-call] found when upgrading FlowmarkTaskCompleted(currentTask, currentTime);// $FlowFixMe[incompatible-use] found when upgrading FlowcurrentTask.isQueued = false;}if (currentTask === peek(taskQueue)) {pop(taskQueue);}advanceTimers(currentTime);}} else {// 直接弹出pop(taskQueue);}// 再从 taskQueue 里面拿一个任务出来currentTask = peek(taskQueue);}// Return whether there's additional workif (currentTask !== null) {// 如果不为空,代表还有更多的任务,那么回头外部的 hasMoreWork 拿到的就也是 truereturn true;} else {// taskQueue 这个队列是空了,那么我们就从 timerQueue 里面去看延时任务const firstTimer = peek(timerQueue);if (firstTimer !== null) {requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);}// 没有进入上面的 if,说明 timerQueue 里面的任务也完了,返回 false,回头外部的 hasMoreWork 拿到的就也是 falsereturn false;}
}
  • flushWork 主要就是在调用 workLoop
  • workLoop 首先有一个 while 循环,该 while 循环保证了能够从任务队列中不停的取任务出来
 while (currentTask !== null &&!(enableSchedulerDebugging && isSchedulerPaused)){// ...}
  • 当然,不是说一直从任务队列里面取任务出来执行就完事儿,每次取出一个任务后,我们还需要一系列的判断
if (currentTask.expirationTime > currentTime &&(!hasTimeRemaining || shouldYieldToHost())) {break;
}
  • currentTask.expirationTime > currentTime 表示任务还没有过期
  • hasTimeRemaining 代表是否有剩余时间
  • shouldYieldToHost 任务是否应该暂停,归还主线程
  • 如果进入 if,说明因为某些原因不能再执行任务,需要立即归还主线程,那么我们就跳出 while

shouldYieldToHost

function shouldYieldToHost() {// getCurrentTime 获取当前时间// startTime 是我们任务开始时的时间,一开始是 -1,之后任务开始时,将任务开始时的时间复值给了它const timeElapsed = getCurrentTime() - startTime;// frameInterval 默认设置的是 5msif (timeElapsed < frameInterval) {// 主线程只被阻塞了一点点时间,远远没达到需要归还的时候return false;}// 如果没有进入上面的 if,说明主线程已经被阻塞了一段时间了// 需要归还主线程if (enableIsInputPending) {if (needsPaint) {// There's a pending paint (signaled by `requestPaint`). Yield now.return true;}if (timeElapsed < continuousInputInterval) {// We haven't blocked the thread for that long. Only yield if there's a// pending discrete input (e.g. click). It's OK if there's pending// continuous input (e.g. mouseover).if (isInputPending !== null) {return isInputPending();}} else if (timeElapsed < maxInterval) {// Yield if there's either a pending discrete or continuous input.if (isInputPending !== null) {return isInputPending(continuousOptions);}} else {// We've blocked the thread for a long time. Even if there's no pending// input, there may be some other scheduled work that we don't know about,// like a network event. Yield now.return true;}}// `isInputPending` isn't available. Yield now.return true;
}
  • 首先计算 timeElapsed,然后判断是否超时,没有的话就返回 false,表示不需要归还,否则就返回 true,表示需要归还。
  • frameInterval 默认设置的是 5ms

advanceTimers

function advanceTimers(currentTime) {// Check for tasks that are no longer delayed and add them to the queue.// 从 timerQueue 里面获取一个任务let timer = peek(timerQueue);// 遍历整个 timerQueuewhile (timer !== null) {if (timer.callback === null) {// 这个任务没有对应的要执行的 callback,直接从这个队列弹出pop(timerQueue);} else if (timer.startTime <= currentTime) {// 进入这个分支,说明当前的任务已经不再是延时任务// 我们需要将其转移到 taskQueuepop(timerQueue);timer.sortIndex = timer.expirationTime;push(taskQueue, timer); // 推入到 taskQueue// ...} else {return;}// 从 timerQueue 里面再取一个新的进行判断timer = peek(timerQueue);}
}
  • 该方法就是遍历整个 timerQueue,查看是否有已经过期的方法,如果有,不是说直接执行,而是将这个过期的方法添加到 taskQueue 里面。

Scheduler调度延时任务

function unstable_scheduleCallback(priorityLevel,callback,options){//...if (startTime > currentTime) {// 调度一个延时任务requestHostTimeout(handleTimeout, startTime - currentTime);} else {// 调度一个普通任务requestHostCallback(flushWork);}
}
  • 可以看到,调度一个延时任务的时候,主要是执行 requestHostTimeout

requestHostTimeout

// 实际上在浏览器环境就是 setTimeout
const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null;/*** * @param {*} callback 就是传入的 handleTimeout* @param {*} ms 延时的时间*/
function requestHostTimeout(callback, ms) {taskTimeoutID = localSetTimeout(() => {callback(getCurrentTime());}, ms);/*** 因此,上面的代码,就可以看作是* id = setTimeout(function(){*    handleTimeout(getCurrentTime())* }, ms)*/
}

可以看到,requestHostTimeout 实际上就是调用 setTimoutout,然后在 setTimeout 中,调用传入的 handleTimeout

handleTimeout

/**** @param {*} currentTime 当前时间*/
function handleTimeout(currentTime) {isHostTimeoutScheduled = false;// 遍历timerQueue,将时间已经到了的延时任务放入到 taskQueueadvanceTimers(currentTime);if (!isHostCallbackScheduled) {if (peek(taskQueue) !== null) {// 从普通任务队列中拿一个任务出来isHostCallbackScheduled = true;// 采用调度普通任务的方式进行调度requestHostCallback(flushWork);} else {// taskQueue任务队列里面是空的// 再从 timerQueue 队列取一个任务出来// peek 是小顶堆中提供的方法const firstTimer = peek(timerQueue);if (firstTimer !== null) {// 取出来了,接下来取出的延时任务仍然使用 requestHostTimeout 进行调度requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);}}}
}
  • handleTimeout 里面主要就是调用 advanceTimers 方法,该方法的作用是将时间已经到了的延时任务放入到 taskQueue,那么现在 taskQueue 里面就有要执行的任务,然后使用 requestHostCallback 进行调度。如果 taskQueue 里面没有任务了,再次从 timerQueue 里面去获取延时任务,然后通过 requestHostTimeout 进行调度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/13223.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++入门系列-赋值运算符重载

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 赋值运算符重载 运算符重载 C为了增强代码的可读性引入了运算符重载&#xff0c;运算符重载是具有特殊函数名的函数&#xff0c;也具有其返回值类型&#xff0c;函数名字以及参…

【GESP试卷】2024年03月Scratch三级试卷

2024年GESP03月认证Scratch三级试卷 分数&#xff1a;100 题数&#xff1a;27 一、单选题(共15题&#xff0c;每题2分&#xff0c;共30分) 010203040506070809101112131415CBACADDADCBCBAB 1、小杨的父母最近刚刚给他买了一块华为手表&#xff0c;他说手表上跑的是鸿蒙&…

24长三角A题思路+分析选题

需要资料的宝子们可以进企鹅获取 A题 问题1&#xff1a;西湖游船上掉落华为 mate 60 pro 手机 1. 手机掉落范围分析 物品特征&#xff1a;华为 mate 60 pro 手机的尺寸、重量、形状等特性。静水假设&#xff1a;西湖水面平静&#xff0c;不考虑水流影响。掉落位置&#xff…

安卓Fragment基础

目录 前言一、基础使用二、动态添加Fragment三、Fragment的生命周期四、Fragment之间进行通信五、Fragment兼容手机和平板示例 前言 Fragment基础使用笔记 一、基础使用 Activity布局和文件 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/andro…

OpenAI 发布 GPT-4o,再次巩固行业地位!

5 月 14 日凌晨 1 点&#xff08;太平洋时间上午 10 点&#xff09;&#xff0c;OpenAI 发布了其最新的 GPT-4o&#xff0c;再次巩固了其在人工智能领域的领导地位。这次发布不仅仅是一个产品的推出&#xff0c;更是向世界宣告 AI 技术已迈入一个全新的纪元。OpenAI 的 CEO 萨姆…

品牌窜货治理管控的方法

窜货问题确实是一个需要品牌方高度关注和有效治理的难题。这种现象通常源于品牌区域销售政策的差异&#xff0c;经销商为了获取更多的利润&#xff0c;往往会利用这些差异进行跨区域的低价销售。这不仅损害了大多数经销商的利益&#xff0c;也破坏了市场的稳定和品牌价值。 品牌…

深入理解 Spring 循环依赖之三级缓存(附源码分析)

前言&#xff1a; 学过 Spring 的都知道 Spring 利用三级缓存解决了循环依赖问题&#xff0c;那你知道什么是循环依赖&#xff1f;什么又是三级缓存&#xff1f;本篇将从源码层面分析 Spring 是怎么去利用三级缓存帮我们解决循环依赖问题。 深入理解 Spring IOC 底层实现机制…

C++ | Leetcode C++题解之第89题格雷编码

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> grayCode(int n) {vector<int> ret(1 << n);for (int i 0; i < ret.size(); i) {ret[i] (i >> 1) ^ i;}return ret;} };

数据结构--红黑树(RBTree)

一、红黑树概念 1.1 什么是红黑树 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长…

自学VBA 设置单元格文字格式 笔记

一.设定对应单元格对应需要显示的格式 Cells(1, 1).Font.Size 18 字体大小 Cells(1, 2).Font.Color RGB(255, 0, 0) 字体颜色 Cells(1, 3).Font.Name "黑体" 字体类型 Cells(1, 4).Font.Italic True 字体斜体 Cells(1, 5).Font.FontStyle "BOLD"…

ubuntu下gcc编译器的安装

.gcc编译器的安装 一般linux下是覆盖含有的&#xff0c;如果没有执行更新命令 sudo apt update gcc安装成功&#xff0c;可以检查一下版本 可以看出我的gcc是9.4.0版本的

SpringBoot环境隔离Profiles

前言 通常我们开发不可能只有一个生产环境&#xff0c;还会有其它的开发&#xff0c;测试&#xff0c;预发布环境等等。为了更好的管理每个环境的配置项&#xff0c;springboot也提供了对应的环境隔离的方法。 直接上干货 知识点 激活环境方法 1&#xff0c;在application…

专用设备制造业供应商收发文件,有什么专业而轻便的方式吗?

专用设备制造业的特点包括&#xff1a;门类广、跨度大、科技含量高。它主要生产的是国民经济各部门&#xff08;包括采掘、化工、冶炼、能源、医疗卫生、环保等&#xff09;所急需的重大成套设备&#xff0c;例如矿产资源井采及露天开采设备、大型火电、水电、核电成套设备、石…

通过内网穿透免费部署我们的springboot+vue项目 实现跟服务器一样的效果

前文讲到通过内网穿透能够实现远程访问个人电脑的静态资源。本文将讲解通过内网穿透实现远程访问本地的项目&#xff0c;实现跟部署到服务器一样的效果&#xff1a;前文链接&#xff1a;通过内网穿透实现远程访问个人电脑资源详细过程&#xff08;免费&#xff09;&#xff08;…

深度学习之卷积神经网络理论基础

深度学习之卷积神经网络理论基础 卷积层的操作&#xff08;Convolutional layer&#xff09; 在提出卷积层的概念之前首先引入图像识别的特点 图像识别的特点 特征具有局部性&#xff1a;老虎重要特征“王字”仅出现在头部区域特征可能出现在任何位置下采样图像&#xff0c…

Python 小抄

Python 备忘单 目录 1.语法和空格 2.注释 3.数字和运算 4.字符串处理 5.列表、元组和字典 6.JSON 7.循环 8.文件处理 9.函数 10.处理日期时间 11.NumPy 12.Pandas 要运行单元格&#xff0c;请按 ShiftEnter 或单击页面顶部的 Run&#xff08;运行&#xff09;。 1.语法和空格…

6---Linux下版本控制器Git的知识点

一、Linux之父与Git的故事&#xff1a; Linux之父叫做“Linus Torvalds”&#xff0c;我们简称为雷纳斯。Linux是开源项目&#xff0c;所以在Linux的早期开发中&#xff0c;许多世界各地的能力各异的程序员都参与到Linux的项目开发中。那时&#xff0c;雷纳斯每天都会收到许许…

VMware Fusion 13.5.2 for Mac 发布,产品订阅模式首个重大变更

VMware Fusion 13.5.2 for Mac 发布&#xff0c;产品订阅模式首个重大变更 适用于基于 Intel 处理器和搭载 Apple 芯片的 Mac 的桌面虚拟化软件 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-fusion-13/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留…

文章解读与仿真程序复现思路——中国电机工程学报EI\CSCD\北大核心《集装箱海港级联物流-能源耦合系统协同优化方法 》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

FPGA - GTX收发器-K码 以及 IBERT IP核使用

一&#xff0c;前言 在FPGA - Xilinx系列高速收发器---GTX中详细介绍了GTX的基础知识&#xff0c;以及IP核的调用&#xff0c;下面将补充一下GTX在使用中的高速串行数据流在接收和发送时的控制与对齐&#xff08;K码&#xff09;&#xff0c;以及高速接口GTX&#xff0c;如果G…