es6 ~ 10
node
几种通用模式
1 | // 《JS二十年》 |
其他示例代码
- reTryRequest: 接口轮询,一分钟内轮询直到成功,超时取消
1 | const queryRequest = (data) => { |
- generator or async/await 实现排序动画
- 设计一个
generator
状态机,和一个栈保留10个不同时间发出的异步任务,取消栈内除了栈顶的所有异步任务
1 | // 《JS二十年》 |
1 | const queryRequest = (data) => { |
generator
状态机,和一个栈保留10个不同时间发出的异步任务,取消栈内除了栈顶的所有异步任务1 | # git:main 分支 |
同样操作一遍,线上的直接404,在.travis.yml脚本中增加了 npm install,就此成功了一次,当第三次推送时又不行了,本地server完全OK,查看node版本,换成.travis.yml中的10.22.0,再clean掉,不行,第五次,第六次,尝试了多种办法还是不行,一气之下改成私有部署,本地1个命令,真香。
1 | deploy: |
1 | # 配置完成后接下来命令行:直接一个命令 |
Next
主题包上.travis.yml
文件内 install
下增加一条安装主题的命令 git clone --branch v8.0.0 https://github.com/next-theme/hexo-theme-next themes/next
即可main
分支上推一下代码即可安装成功挑战类型体操答案合集,一部分是自己做的,一部分是自己抄的
1 | // 期望是一个 string 类型 👉 so eazy |
1 | // typeof Type Operator |
Pick<T, Keys>
1 | interface Todo { |
Readonly<T>
1 | interface Todo { |
DeepReadonly<T>
🌟1 | type DeepReadonly<T> = { |
ReadonlyAndPick<T, Keys>
🌟1 | type ReadonlyAndPick<T, K extends keyof T> = {readonly [P in keyof T as P extends K ? P : never]: T[P]} & {[P in keyof T as P extends K ? never : P]:[P]} |
Omit<T, Keys>
1 | interface Todo { |
Exclude<T, U>
1 | // 从T中排除可分配给U的类型 |
ReturnType<T>
提取函数的返回类型1 | type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any |
Parameters<T>
提取函数的参数类型1 | type Parameters<T> = T extends (...args: infer P) => any ? P : T |
First<T>
of Array 实现一个泛型类型, 使得可以返回数组类型的第一项1 | type arr1 = ['a', 'b', 'c'] |
Length<T>
of Tuple1 | type tesla = ['tesla', 'model3', 'model X', 'model Y'] |
Last<T>
of Array 实现一个泛型类型, 使得可以返回数组类型的最后一项1 | type arr1 = ['a', 'b', 'c'] |
Concat<T, P>
: 实现一个Array.concat 方法功能相同的类型1 | type Concat<T, P> =T extends any[] ? P extends any[] ? [...T, ...P] : never : never; |
Push<T, P>
: Array.push 类型方法1 | type Push<T extends any[], P extends any> = [...T, P] |
Unshift<T, P>
: Array.unshift 类型方法1 | type Unshift<T extends any[], P extends any> = [P, ...T] |
Pop<T>
🌟1 | type Pop<T> = T extends [...infer P, infer E] ? P : never; |
Shift<T>
🌟1 | type Shift<T> = T extends [infer E, ...infer P] ? P : never; |
Promise.all
🌟🌟1 | type inferTupleT<T extends any[]> = { |
If<truthy, T, F>
🌟🌟
If<true, T, F> => T, If<false, T, F> => F
这里还需要考虑
any
,never
,boolean
被视为union type
, 同时 never是union运算的幺元【可在 category-theory-for-programmers 这本书上找到相关解释】, 但是unknown
却没有
1 | type If<C, T, F> = C extends true ? T : C extends false ? F : never; |
distributive conditional types 的3个前提条件:1. T = naked type; 2. T = checked type; 3. T 实例化为 union type;
1 | // T: checkedType 被检查的类型 |
TupleToUnion<T>
元组转联合类型1 | type TupleToUnion<T extends any[]> = T[number] |
TupleToObject<T>
元组转对象1 | type tuple = ['tesla', 'model 3', 'model X', 'model Y'] |
IsNever<T>
🌟conditional type 判断 IsNever 在某种情况下有问题:如果这么写
type IsNever<T> = T extends never ? true : false
, 当type A = IsNever<never>
时type A = never
而不是 true or false, 查阅怎么理解 conditional type 中的联合类型与 never
1 | // IsNever |
Flatten<T>
1 | type Flatten<T> = T extends Array<infer I> ? I : T; |
特别说明:本项目只关注如何写可用 ts 类型,其他诸如:根路径配置类型文件、ts 类型查找顺序、ts 类型体操等均不涉及
1 | # 文件名及文件层级 |
*.vue
文件内1 | // some-detail.vue script.lang = ts |
server/api.ts
接口定义,接口通用 axios 实例类型返回动态类型store/modules
state 定义,融合多个模块的类型router/mpdules
routes 定义,扩展字段类型*.vue
文件内 setup
& composables/*.ts
逻辑组件, 内部:函数声明,变量定义,赋值操作等any
const a = ref('some str'); // a 一定会被推导为 Ref<string>
1 | // 以上写法可以改成? |
扩展全局模块类型: router/store/axios
衍生问题,如何知道 router 上有 RouteMeta 类型?(或者说如何知道 Router 有哪些类型)
1 | // 扩展全局模块类型router/store/axios,以router为例 |
composables/directives/server
Method 是什么类型?
如何修改 request 的定义,使得可以在使用getData
是返回与后端确定好的结构?在修改过程中碰到哪些问题?请举例
1 | // server/index |
1 | // 一开始可能只知道某个变量的几个属性,大致结构,不确定后续属性的新增/减少,可以这样写 |
1 | // 字面量类型 |
把类型当作值的集合,理解 类型中的 union type - 交集 和 intersection type -并集
1 | type AA = {aa: 'A'} |
从以上例子中考虑:
联合类型
和交叉类型
的区别? 可以得出交叉类型的应用场景是什么?
实际上,项目中使用最常见的还是联合类型和单一的type/interface, 使用 交叉类型 较多的场景是 store
,为什么?
new Promise
, new Map
, new Set
等1 | new Promise<T>((resolve, reject) => { |
考虑众多的类型定义,引用,需要对所有类型进行划分,文件结构梳理,有哪几种方式?考虑以上涉及到的文件和使用地方,类型导出声明的几种方式,类型分布项目中的文件结构可能有哪几种?
server/api.ts
, store/modules
, components/*.vue
提炼相近的结构
1 | // 以api.ts为例:前置条件为 基础方法都已经封装完毕,直接使用即可:get, post, put等 |
使用泛型,先不考虑合理的问题
1 | // 考虑一种情况,返回类型由参数类型决定,此时最简单的就是泛型 |
将所用类型分类拆分,大量使用联合类型、交叉类型
1 | // 考虑一种实际业务情况,一位客户的公开信息由:基础信息 & 会员信息 & 订单信息组成 |
使用部分联合类型时,考虑用 Pick
Omit
等方法
1 | // 可选 |
1 | // some-detail.vue script.lang = ts |
温馨提示: 先别替换 ant-design-vue 中的 moment 为 dayjs, 有bug, date-picker会坏掉,等作者修好再换
本项目的技术栈是:vite vue3 ant-desing-vue
。
项目中需要用到的ant-desing-vue
UI 组件依赖moment
,我看官网提示有插件可以改成dayjs
,可以使整个包小一点,看了antd-dayjs-webpack-plugin
的代码后发现没有针对vite ant-desing-vue
的,就跃跃欲试想写一个vite-plugin-vue-ant-desing-vue-dayjs
插件,专门用在vite vue3 ant-desing-vue
项目中。后面其实发现没必要,当然,写完才发现-_-||
antd-dayjs-webpack-plugin
代码,内部是module.exports
export default vitePluginVueAntDVueDayjs(Options={}) {...}
yarn add git+ssh://git@github.com:xxx/some-project.git --dev
,这个some-project
就是写的插件,package.json
里暴露的直接是main: src/index.js
,第一次写插件,心情十分激动结果刚运行就报错,搜了一下原来是 node 环境里不能直接加载ES6
模块。。。于是我重新看了一次 es6 文档node 环境如何加载es6模块
,然后查了一下@vite/plugin-vue
的解决办法
module.export
-> 针对入口文件非打包后的插件。如package.json中的main: src/index.js
esbuild src/index.ts --bundle --platform=node --target=node12 --external:@vue/compiler-sfc --outfile=dist/index.js
-> 完全使用 es6 模块编写插件,然后使用打包后的 cjs 模块, package.json中的main: dist/index.js
1
2
3
# 命令行
# npm i dayjs
yarn add dayjs
1
2
3
4
5
6
7
8
// vite.config.js文件
{
resolve: {
alias: {
moment: 'dayjs'
}
},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// main.ts
import dayjs from 'dayjs'
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
import advancedFormat from 'dayjs/plugin/advancedFormat'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import weekday from 'dayjs/plugin/weekday'
import weekOfYear from 'dayjs/plugin/weekOfYear'
import isMoment from 'dayjs/plugin/isMoment'
import localeData from 'dayjs/plugin/localeData'
import localizedFormat from 'dayjs/plugin/localizedFormat'
import 'dayjs/locale/zh-cn' // 导入本地化语言
dayjs.extend(isSameOrBefore)
dayjs.extend(isSameOrAfter)
dayjs.extend(advancedFormat)
dayjs.extend(customParseFormat)
dayjs.extend(weekday)
dayjs.extend(weekOfYear)
dayjs.extend(isMoment)
dayjs.extend(localeData)
dayjs.extend(localizedFormat)
dayjs.locale('zh-cn') // 使用本地化语言
如果要写也不是不行:看上面的配置步骤即可知道思路,可参考项目vite-plugin-vue-antd-dayjs
有个直接的好处,安装后可以直接修改包文件来测试代码逻辑是否正确,即改即用,我当时采用的就是这种写法
1 | # 项目结构 |
1 | // packgae.json |
1 | // src/index.js |
1 | # 安装 插件的 peerDependencies |
1 | // 在 vite.config内配置 |
1 | // 以 isMoment 为例 |
启动项目:
yarn dev
, vite 打包会发现有提示:[vite] new dependencies found: dayjs/plugin/isSameOrAfter, dayjs/plugin/advancedFormat, dayjs/plugin/customParseFormat, dayjs/plugin/weekday, dayjs/plugin/weekYear, dayjs/plugin/weekOfYear, dayjs/plugin/isMoment, dayjs/plugin/localeData, dayjs/plugin/localizedFormat, dayjs/locale/zh-cn, updating...
说明已经成功引入
步骤跟上面的差不多,只是写的时候使用 es6 模块,多一步打包&入口文件有点不同,可参考项目vite-plugin-antd-vue-ts
packgae.json 的配置
1 | { |
入口文件
1 | export default function VitePluginVueXXX(Options = {}) {} |
1 | npm run build:boudle |
vite.config.js
& 在main.js
引入文件步骤同上如果要实现在入口文件的引用方式为
import someplugin from 'path/to/all-plugin-file-name'
,在插件内使用export default
和module.exports
是一样的效果
结尾提供了其他相关链接
使用 nvm 切换不同版本的 node 来适用不同项目
1 | node -v |
1 | { |
打开 vite.config.ts 文件时 控制台就会报 failed to load the eslint library for the vite.config.ts 错误,这是因为本地 eslint 无法解析 ts 文件,需要安装 ts 依赖 & vite 不提供 ts 类型检查
1 | # 安装ts eslint 依赖 |
下载额外的
npm
包需要添加其他类型定义:1.安装npm i --save-dev @types/xxx
;2.在tsconfig.json
中添加``types`的配置『 由于 vite2 创建的 ts 项目中已添加默认的 types 定义,标明只使用该类型定义,所以如果需要其他类型(不在@vite/client 中),需要增加配置 』
1 | // tsconfig.json |
本项目使用 eslint:recommended 规范,如需修改请参照 tslint 官方提示
工作目录下新增 .eslintrc.js
文件
1 | // 不含 eslint-plugin-vue 插件的配置 |
工作目录下新增 .eslintignore
文件
1 | # don't ever lint node_modules |
1 | // server/index.ts 文件 |
post, get
等方法1 | // const-api-path.ts |
测试环境的发布分支只有 2 个: dev, hotfix
基础仓库影响范围较大,分别对应多个项目的不同阶段,则需要对该项目进行分支管理和保护,通常只保护3个主要分支: main, dev,hotfix
一个新需求的全部工作流程
feature-
开头dev
提 merge request
dev
后,开始测试merge request
到 main
分支准备发布tag
, 并推送 tags一个紧急修复的全部流程
git checkout hotfix
git pull
main
分支准备发布tag
, 并推送 tags一个主仓库分别由各个开发fork到自己的group, 主仓库只保留3个分支:main(稳定版本-线上环境), hotfix(紧急修复-发布测试环境), dev(开发时-发布测试环境), fork仓库完全由开发者自行管理
1 | # fork后的项目新增一个 remote |
.env.production
文件.env.development
文件.env.stagging
文件1 | import { defineConfig } from "vite"; |
1 | yarn add vue-router@4 |
1 | import HelloWorld from "@/components/hello-world.vue"; |
1 | // augmenation.d.ts |
ant-design-vue
, 所以选用 less, 方便覆盖默认主题1 | // 使用插件转换文件 |
请匹配 组件库 的选择
1 | # .scss and .sass |
yarn add dayjs
or npm i dayjs
在 vite.config
中添加 alias
1 | { |
在 main.ts
文件里添加相关插件, 添加前后分别打包进行比较
1 | // main.ts |
reaåctive()
: 返回对象的响应式副本SetUp(props, context) 逻辑块组合,可组合:生命周期函数/watch/computed
,返回对象,对象可包含 data/methods/computed
beforeCreate
setup()
created
setup()
beforeMount
-> onBeforeMount
mounted
-> onMounted
beforeUpdate
-> onBeforeUpdate
updated
-> onUpdated
beforeUnmount
-> onBeforeUnmount
unmounted
-> onUnmounted
errorCaptured
-> onErrorCaptured
renderTracked
-> onRenderTracked
renderTriggered
-> onRenderTriggered
由于
props
是响应式的,所以不能使用ES6解构
,因为 解构是浅复制,非引用类型属性拷贝后会跟原 prop 属性脱离,是一个新的变量,没有响应性,引用类型的属性(数组,对象,函数)还会保持引用,这 2 种情况下解构后的变量响应性不统一 所以会消除 prop 的响应性,如果需要解构 prop,使用toRefs
若要获取传递给 setup() 的参数的类型推断,请使用
defineComponent
请使用
useRouter()
||useRoute()
, 模板中可以访问$router
和$route
,所以不需要在 setup 中返回router
或route
userLink & RouterLink
: 内部行为已经作为一个组合式 API 函数公开setup()
函数中,不需要将类型传递给 props 参数,因为它将从 props 组件选项推断类型refs()
1.初始值推断类型;2.传递一个泛型参数;3. 使用Ref<T>
替代ref
readtive()
使用接口(Interface
)computed
自动推断1 | import { defineComponent, PropType } from "vue"; |
validators
和 default
值1 | import { defineComponent, PropType } from "vue"; |
1 | const Component = defineComponent({ |
1 | const year = ref<string | number>("2020"); // year's type: Ref<string | number> |
与
ref
的区别: 当变量基础类型时使用ref
, 引用类型-对象/数组等使用reactive
1 | import { defineComponent, reactive } from "vue"; |
const FSP = require('fs/promises');
windows 系统的同学可将 build 打包命令由
vuedx-typecheck . && vite build
改为vite build && tsc
,ci 脚本内加一条:run: npx --no-install vuedx-typecheck
ts
文件内使用 const xxx = require('xxx')
报错:require is not defined
已解决:
vite@2.4.1
已修复该错误,升级即可
ant-design-vue 按需加载问题:
查看Imports using @ant-design/icons-vue should be importing the ESM version
and-design-vue 版本问题!!!现发现 form & input
组件在引入 es 包时一直报错The requesed module '/node_modules/is-mobile/index.js' is dose not provide an export named 'isMobile'
,遂升级至 2.1.1 版本,同时删除 vite.config 里 optimizeDeps 的 exclude 的配置,才能使用:
详见组件库依赖的 ismobilejs 存在兼容性问题
正常:移动光标修改
插入文本: 键入 i 进入插入模式
替换文本:键入 R 进入替换模式
可视化: 选中文本块,键入 v 进入可视化一般模式,V 进入可视化行模式,Ctrl+v 可视化块模式
执行命令:键入 : 进入命令模式:
:q
退出关闭窗口:w
保存(写):wq
保存并退出:e {file-name}
打开要编辑的文件:ls
显示打开的缓存:help {标题}
打开帮助文档::help :w
打开:w
命令的帮助文档:help w
打开w
移动的帮助文档键入
ESC
切换为正常模式
hjkl(左下右上-逆时针)
w
-下一个词,b
-词初,e
-词尾0
行初,^
-第一个非空格字符,$
行尾H
-屏幕首行,M
-屏幕中间,L
-屏幕底部Ctrl+u
上翻,Ctrl+d
下翻gg
-文件头,G
文件尾部:{line number}<CR>
or {line number}G
f{character}, t{character}, F{character}, T{character}
, (f/F = find, t/T = to, 小写是 forward, 大写是 backward),
or ;
用于导航匹配/{regex}
向后搜索,n
or N
用于导航匹配i
- insert modeo
/O
insert line below / above 向下、上插入行d{motion}
删除{移动命令},dw
删除词,d$
删除到行尾,d0
-删除到行头c{motion}
改变{移动命令},cw
改变词,d{motion}
再i
x
删除字符,等同于dl
s
替换字符,等同于xi
d
删除或者c
改变u
撤销,<C-r>
重做y
to copy / “yank” (some other commands like d also copy)p
粘贴~
改变字符大小写
3w
向前移动 3 个词,5j
向下移动 5 行,7dw
删除 7 个词
i
,a
表示在内部,在周围
ci(
改变当前括号内容ci[
改变当前方括号内容da'
删除一个单引号字符串,包括周围的单引号宏(macro):批处理,命令集合
搜索&替换::s
替换
多窗口::sp
or :vsp
分割窗口,同一个缓存可以在多个窗口显示
1 | # 以下载的 vimrc 文件为例 |
宏:q{character}
开始在寄存器{character}
中录制宏,q
停止录制,@
重放宏
执行宏 {次数}@{字符}
,执行宏{次数}
次
宏递归:
q{character}q
清除宏1 | # 自动加末尾行注释宏**@t** |
1 | { |
基于 macOS,有些是自定义后的快捷键,请
command + k; command s
查看自己的键盘快捷方式
command + k; command s
查看键盘快捷方式command + p
搜索文件command + shift + p
打开命令面板command + t
全局搜索文件内容command + w
关闭当前文件command + shift + n
新建窗口command + shift + v
预览 markdown 文件command + shift + k
删除光标所在的一行command + /
切换单行注释command + shift + /
切换块注释command + j
切换终端command + b
切换侧边栏command + ,
打开设置command + f2 + fn
选择当前文件内所有和光标所在位置相同的单词 command + 1 or 2 or 3
编辑区分区command + l
选择当前行command + [ or ]
缩进command + k; command + o
全部折叠command + k; command + l
切换折叠command + option + [ or ]
折叠/展开代码块command + k; command + [ or ]
递归折叠/展开代码块command + shift + l
先执行一次 选择,再执行此命令可选择到所有相同文本或选项command + option + shift + 上下或左右箭头
选择多行代码option + shift + 拖动光标
选择多行代码option + shift + 上下箭头
向上或向下复制行option + shift + 左右箭头
向左或向右选择部分代码option + 上下箭头
向上或向下移动行option + 上下箭头
向上或向下移动行f3 + fn
在结果中导航command + d
选择多个结果command + g
查找下一个command + shift + g
查找上一个option + enter
选择所有结果f8 + fn
导航到错误或警告处其余教辅资料
请向contact@epubit.com.cn
发送邮件获取启动程序的过程:操作系统将程序的 代码
和静态数据
从磁盘加载到内存中,同时为程序的运行时栈
和动态内存堆
分配内存,最后启动程序,通常是程序入口(以c
为例通常是 main()函数)。
默认情况下进程都有3个文件描述符
0
1
2
1 | # 进程主要状态 |
暂时只考虑单 CPU 的情况,不管是什么进程,CPU 都有可能不断中断,切换任意进程,记住就算是亲子(进程)也是不存在尊老爱幼的,谁先运行取决于 CPU 调度程序,所以执行顺序是不确定的。⚠️ 这种不确定性贯穿全文
1 | // 有必要理解一下返回2次, 示例代码(c)如下: |
‼️ 注意:nodejs child_process.fork()虽然调用的是 fork(2),但是却有所不同,衍生的 child progress 不会克隆当前 parent progress,只能通过 IPC 通信。
1 | # 命令行执行: node ./os-notes/chapter-5.js 输出结果: |
其他变体:execl(), execle(), execlp(), execv(), execvp()等。‼️ 成功调用不会返回。
wait()
来确保自己后运行,此时 2 种情况:wait()
,该系统调用会在 child progress 运行结束后返回,然后 parent progress 输出。‼️ 问题:多进程程序是怎么抽象的?
进程是程序运行的一个实例,多进程就是程序运行的多个实例,
fork()
函数在新的 child progress 中运行相同的程序,child progress 是 parent progress 的复制品,进程上下文中将只有栈
内数据不同(通过标记为私有的写时复制,复制时是完全相同的,运行后不同),堆
、代码
中的数据完全相同,因为 fork 复制进程时只是重新分配了一段虚拟内存复制原进程内容,并且标记为只读,可以通过绘制进程图来了解 fork()。而通过exec()
是在当前进程中的上下文中运行另一个新程序,将覆盖当前进程的地址空间(该进程所在的虚拟内存),但是PID
(进程 id)不变且继承了调用该函数时已打开的所有文件描述符(即包含stdin
,stdout
,stderr
3 个描述符)。更多内容请参考csapp第八章异常控制流
一起食用。
LDE 协议的两个过程:1. 内核初始化陷阱表,并记住位置以便后续执行操作;2. 在进程中设置节点分配内存,用于保存陷入陷阱和返回的信息
此协议下需要解决 2 个问题:
1 | # 进程的地址空间: |
如何开发调度策略? 想一想什么情况下要使用调度策略?然后弄清楚评价调度策略的关键指标是什么?
工作负载假设:
1. 每一个进程运行相同的时间 -> 完全公平
2. 所有工作同时到达 -> 平均响应时间
3. 一旦开始,每个进程运行到完成 -> 运行时独占CPU
4. 所有进程不涉及I/O -> 进程任务单一
5. 已知每个进程的运行时间 -> 平均周转时间
调度指标:
1. 周转时间 = T完成时间 - T到达时间 => 性能指标
2. 公平
3. 响应时间 = T首次运行 - T到达时间
让我们设置一个时间函数 T(参与进程, 策略, [放开的假设条件])
1 | # 工作进程假设: 基于逐渐放开以上5个假设条件 |
平均周转时间:
平均周转时间作为唯一指标的话,STCF表现良好。然鹅,引入分时系统后用户要求系统交互性好,所以引入响应时间作为新的指标
平均响应时间:Tr(test4,STCF,[1,2,3]) = (0 + 10 - 10 + 20 - 10) / 3 = 3.3s;
Tr(test1,FIFO,[]) = (0 + 10 - 10 + 20 - 10) / 3 = 3.3s;
两种调度程序:SJF,STCF 优化了周转时间,不利于响应时间;RR 优化了响应时间,不利于周转时间,接下来需要放开假设 4 和假设 5
结合I/O: 重叠,当一个进程被I/O阻塞,CPU可以切换其他进程运行
工作进程假设:A 50ms 运行10ms 发出I/O; B 50ms 没有I/O
1 | # 没有重叠 |
实际上调度程序并不知道每个工作的长度,下一章将通过构建一个调度程序利用最近的使用情况预测未来从而解决未知工作长度的问题:多级反馈队列
这是我遇到的第二个多级,真是相当美妙的思想,第一个多级是多级页表(这本书它出现在第二章,我发誓我没有跳着看书 🐶)
多级反馈队列要解决 2 个问题:优化周转时间 && 降低响应时间 => 从历史中学习并预测
MLFQ基本规则:
MLFQ有许多独立的队列(queue),每个队列有不同的优先级(priority level)。一个进程只能在一个队列中,MLFQ总是优先执行较高优先级的进程,每个队列中的进程都拥有相同的优先级。
MLFQ的关键在于如何设置优先级。
规则1:若A的优先级 > B的优先级,运行A
规则2:如果A的优先级 == B的优先级,RR(轮转)运行
刚定下 2 个规则就出现一个问题:AB 两个进程在高优先级队列中,CD 两个进程在低优先级队列中,假设 AB 任务一直运行,CD 岂不是等到死都没法运行
如何改变优先级:
先考虑工作负载的类型:1.运行时间短,频繁放弃CPU的交互型工作;2.需要更多CPU时间,响应时间不重要的长时间计算密集型工作。
所以调整算法,增加规则:
规则3:工作进入系统,放到最高优先级 - 单个长工作(密集计算型)
规则4a:工作用完整个时间片后,降低到下一个优先级 - 长工作运行一段时间来了一个短工作(交互型)
规则4b:如果工作在时间片内动释放CPU,则优先级不变 - 交互型短工作执行大量I/O操作
以上实例皆运行良好,但是会出现长工作的饥饿问题:
系统中出现了大量的交互型短工作,导致长工作无法得到CPU
||
提升优先级:S太高长工作会饿死,太低交互型工作得不到合适的CPU时间比例
规则5:周期性提升所有工作的优先级,经过一段时间S,将系统中的素有工作重新加入最高优先级队列
更好的计时方式:修改规则4a & 4b:
规则4:一旦工作用来了某一层的时间配额(无论中间主动放弃了多少次CPU),就降低优先级 => 防止恶意程序愚弄CPU
MLFQ 调优及其他问题
配置多少优先级队列?
每一层队列的时间片设置为多大?
多久提升一次进程的优先级?
大多数的 MLFQ 变体都支持不同队列可变的时间片长度。高优先级队列通常只有较短的时间片,因此这一层的交互工作可以更快的切换,相反,低优先级队列更多的是密集型工作
1 | # 伪代码 |
基于事件的服务器如何决定事件的发生顺序?尤其是网络和 I/O
这里我们有一个例子,来首先了解一下select()函数:这个例子是从CSAPP上抄来的,select真是令人印象深刻
假设有一个echo服务器,它很强,一边接收网络请求,一边也可以响应用户的命令行键入请求。肿么办捏,先处理哪一个?更具体的代码示例请参考`《CSAPP》第12章第2节基于I/O多路复用的并发编程`
基于I/O多路复用技术的并发:使用select(),要求内核挂起进程,只有在一个或者多个I/O发生后才将控制权返回给应用程序(说人话:多个I/O请求注册到同一个select, select搞一个集合存储他们的状态,只要有I/O触发那么就由系统调用转回应用程序(用户模式))。
select() 检查I/O描述符集合, 地址通过`readfds`,`writefds`, `errorfds`传入;
在每个集合中检查前nfds个描述符。
返回时,select()用给定请求操作准备好的描述符组成的子集替换给定的描述符集合,返回所有集合中就绪描述符的总数。
请注意 select()的超时参数,常见用法是设置为 NULL,但会导致无限期阻塞,直到有可用的就绪描述符。更好的做法是将超时设置为 0,因此让调用的 select()立即返回,这种方式提供了一种构建非阻塞(异步)事件循环的方法。
补充:阻塞与非阻塞接口
阻塞(同步)接口:在返回给调用者之前完成所有工作,非阻塞(异步)接口开始一些工作但是立即返回,从而让所有需要完成的工作都在后台完成。
通常阻塞调用的就是某种I/O。
非阻塞接口可用于任何类型的编程(例如使用线程),但在基于事件的方法中非常重要,因为阻塞的调用会阻塞所有进展。
‼️ 要分清楚并不是所有的非阻塞=异步,I/O 就不是,非阻塞 I/O!==异步 I/O
使用单个 CPU 和基于事件的并发服务器,线程并发的程序中存在的抢占锁释放锁等问题不复存在,因为只有一个线程,不会被其他线程中断,但是请务必记住‼️ 不要阻塞基于事件的服务器,即 node 中,小心使用同步 api.
阻塞系统的调用:I/O 大 boss 如何解决?
1 | // AIO control block |
补充:UNIX信号
信号提供了一种与进程通信的方式,可以将信号传递给应用程序,进程将暂停当前工作开始运行信号处理程序。完成后,该进程就回复先前的行为。
每个信号都有名字,如:HUP(挂断),INT(中断),SEGV(段违规)
内核或者程序都可以发出信号
可以用kill命令行工具发出信号,例如:
prompt > ./main & [3] 36705
prompt > kill -HUP 36705
stop wakin' me up...
在没有异步 I/O 的系统中,纯基于事件的方法无法实现。然鹅可以使用某种混合方法,使用线程池来管理未完成的 I/O。参考《flash: an efficient and portable web server》来了解更多。
当事件处理程序(事件循环中待处理的一个事件)发出异步I/O时,必须打包一些程序状态,以便下一个事件处理程序在I/O完成完成时使用。而基于线程的工作是不需要的,因为状态都保存在线程栈内。
node是如何处理这个问题的?
据鄙人看完的《深入浅出nodejs》后的一些笔记来看,node只是JS运行在单线程上,异步I/O另有线程:
部分线程通过阻塞或非阻塞&轮询技术来完成数据获取,一个线程进行计算处理,通过线程间通信进行数据传递,实现异步I/O,也就是说node处理异步I/O是通过管理线程池来实现的。(没想到吧Σ(⊙▽⊙"a)
当系统是多核 CPU 时,基于事件的一些简单性(不用加锁,不存在线程中断)就没有了,为了利用多个 CPU,事件服务器必须并行运行多个事件处理程序,这时会出现「缓存一致性」?「加锁!必须加锁!!」,「发生缺页肿么办!被堵死了」等各种问题,现在先不要心塞,后面心塞的还多着呢,抽屉里放点硝酸甘油片啥的 ❤️。
思考:同步 I/O 包含非阻塞 I/O 吗?异步 I/O 呢?
I/O 包含两步:请求和数据复制到缓冲区
I/O 类型 | 请求 | 数据复制到缓冲区(aio_buf) | 检查 I/O 是否完成的方法 |
---|---|---|---|
同步 I/O | 阻塞 | 阻塞 | 等前两步完成也就拿到了 |
非阻塞 I/O | 异步 | 阻塞 | 轮询,CPU 很忙 |
I/O 多路复用 | 异步 | 阻塞 | 轮询,CPU 很忙(linux: epoll;) |
异步 I/O | 异步 | 异步 | 中断时发信号(windows:IOCP) |
node 基于 I/O(AIO(通过线程池来实现异步 I/O))多路复用,所以是单线程(JS)、非阻塞、异步 I/O,JS 运行的单线程不会阻塞,但是 I/O 过程有可能是阻塞的,一旦遇到缺页就会卡住,就算主动去非洲客户也不会原谅你,OMG 生活真是苦涩。
思考: node 单线程是怎么实现并发的?
nodejs使用cluster(集群)创建多个共享服务器端口的child progress来利用多核CPU系统
原理是:child_process.fork()衍生的是独立的child progress,和 parent progress只通过IPC管道来通信,可以根据需要随时关闭或者创建,不影响其他进程。
cluster分发连接:主进程负责监听接口,然后循环分发给各child progress。
思考:多个进程为什么可以共享服务器端口?
首先要知道监听描述符和已连接描述的区别,是并发服务器的基础,每次一个请求到达监听描述符时,可以在此时派发fork()一个新进程来连接已连接描述符与客户端通信。服务器调用server.listen({fd:7})将消息发给主进程,parent progress将监听监听描述符并将句柄(handle)派发给child progress,child progress调用server.listen(handle)会显式监听handle而不是与 parent progress通信,child progress还会调用server.listen(0)会收到相同的「随机端口」,随机端口在第一次随机分配,而后都是已知的。
如果需要独立端口,可根据child progress的PID来生成。
思考 again: nodejs 主进程挂了肿么办?所有相关 child progress 会一起被回收还是移交给主进程挂掉时立即重启一个新的 parent progress 呢?
child progress死亡不会影响 parent progress, 不过child progress死亡时(线程组的最后一个线程,通常是“领头”线程死亡时),会向它的 parent progress发送死亡信号. 反之 parent progress死亡, 一般情况下child progress也会随之死亡, 但如果此时child progress处于可运行态、僵死状态等等的话, child progress将被进程1(init 进程,由内核创建,是所有进程的祖先)收养,从而成为孤儿进程. 另外, child progress死亡的时候(处于“终止状态”),parent progress没有及时调用 wait() 或 waitpid() 来返回死亡进程的相关信息,此时child progress还有一个 PCB (Process Control Block进程控制结构)残留在进程表中,被称作僵尸进程.
思考:让我们来想想 nodejs 进程管理 是怎么实现的?
container
: 容器,一个被隔离的且有自己的文件系统,网络的正常的操作系统进程,进程树独立于主机image
: 镜像,一个配置文件用来生成容器的docker run your-Docker-image
,docker ps --all
docker build --tag tagName:Vsesion .
docker run --publish 8000:8080 --detach --name container-alias-name tagName:Vsesion
docker rm --force container-alias-name
1 | # FORM: 从 node:12.18.1 这个线上的image继承 |
1 |