Skip to content

Commit

Permalink
docs: update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
dingyuqi committed Dec 2, 2024
1 parent aa2df73 commit 4f7fc8a
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 61 deletions.
38 changes: 20 additions & 18 deletions docs/2. 编程语言/03.2021-10-18-前后端常用词汇区分.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ p {

## JavaScript
JavaScript 对网页行为进行编程, 其作用有:
1. 能够改变 HTML 内容
2. 能够改变 HTML 属性
3. 能够改变 CSS 样式
4. 能够隐藏 HTML 元素
5. 能够显示隐藏的 HTML 元素
1. 能够改变 HTML 内容.
2. 能够改变 HTML 属性.
3. 能够改变 CSS 样式.
4. 能够隐藏 HTML 元素.
5. 能够显示隐藏的 HTML 元素.

简单的 js 可以包含在html文件中, 放在 `<script></script>` 之中, 也可以分开来写单独的 `.js` 文件.

Expand All @@ -81,8 +81,8 @@ jQuery 是一个 JavaScript 库. jQuery 极大地简化了 JavaScript 编程.
这是为了防止文档在完全加载(就绪)之前运行 jQuery 代码.

如果在文档没有完全加载之前就运行函数, 操作可能失败. 下面是两个具体的例子:
1. 试图隐藏一个不存在的元素
2. 获得未完全加载的图像的大小
1. 试图隐藏一个不存在的元素.
2. 获得未完全加载的图像的大小.

## AJAX
AJAX 是一种在无需重新加载整个网页的情况下, 能够更新部分网页的技术. AJAX 用于浏览器与服务器通信而无需刷新整个页面, 服务器将不再返回整个页面, 而是返回少量数据, 通过 JavaScript DOM 更新一部分节点. 期间数据传输可采用 xml, json 等格式, AJAX 最早用于谷歌的搜索提示.
Expand Down Expand Up @@ -142,25 +142,25 @@ xmlhttp.onreadystatechange=function()

而 JQuery 发送 AJAX 分为 5 个步骤:
::: steps
1. 传输的 url
1. 传输的 url.

也就是所谓的数据往哪个地址拿数据
也就是所谓的数据往哪个地址拿数据.

2. type
2. type.

请求的类型 比如有: post/ put/ get/ delete
请求的类型 比如有: POST, PUT, GET, DELETE 等.

3. data
3. data.

要传输给后端的数据, 没有可以不写.

4. dataType:'json'
4. dataType:'json'.

如果Php端声明了 json 返回, 那么 js 端可以不用写 `dataType="json"`.
如果 Php 端声明了 json 返回, 那么 js 端可以不用写 `dataType="json"`.

如果 Php 端没有声明 json 返回, 那么 js 端必须写 `dataType="json"`.

5. success
5. success.

发送数据之后成功的回调函数.
:::
Expand Down Expand Up @@ -235,9 +235,11 @@ def greet():
:::

::: tip PHP
PHP(全称: PHP: Hypertext Preprocessor, 即"PHP: 超文本预处理器")是一种通用开源脚本语言. PHP 脚本在服务器上执行.
PHP(全称: Hypertext Preprocessor, 即 "超文本预处理器")是一种通用开源脚本语言, 在服务器上执行.

PHP 可以收集表单数据; 可以发送和接收 cookies; 可以添加、删除、修改您的数据库中的数据;
PHP 可以收集表单数据, 可以发送和接收 cookies, 可以添加、删除、修改数据库中的数据.
:::

扩展阅读: [AJAX 访问数据库实例](https://www.w3school.com.cn/tiy/t.asp?f=ajax_database)
::: info 扩展阅读
1. [AJAX 访问数据库实例](https://www.w3school.com.cn/tiy/t.asp?f=ajax_database)
:::
35 changes: 18 additions & 17 deletions docs/2. 编程语言/06.2023-10-30-Cgo 调用实战.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Cgo 的调用主要分为两种:
1. 将 C 语言代码以注释的形式放入 Go 语言文件中.
2. 将 C 语言代码通过共享库的形式提供给 Go 源码.

本文章中两种方式都会提到, 但是主要讲第二种方法. 在讲述第二种方法时, 将以 Go 语言调用 C++ 库-- Armadillo 为例.
本文章中两种方式都会提到, 但是主要讲第二种方法. 在讲述第二种方法时, 将以 Go 语言调用 C++ 库-- [Armadillo](https://arma.sourceforge.net/) 为例.

文章将先讲述 Cgo 的原理, 再讲述两种方式的调用. 如果只想看示例代码的可以跳到后面.
<!-- more -->
Expand All @@ -19,7 +19,10 @@ Cgo 的调用主要分为两种:
- [在 Go 中链接外部 C 库](/article/bi7v3j65/#在-go-中链接外部-c-库)

## Cgo 的使用场景
在以下的一些场景中, 我们可能会无法避免地使用 Cgo. 但是我们必须明白的是, Cgo 的使用需要付出一定的成本, 且其复杂性极高, 难以驾驭, 所以需要谨慎使用.
在以下的一些场景中, 我们可能会无法避免地使用 Cgo.

::: note 但是我们必须明白的是, Cgo 的使用需要付出一定的成本, 且其复杂性极高, 难以驾驭, 所以需要谨慎使用.
:::

1. 为了提升局部代码性能, 使用 C 代码替换一些 Go 的代码. 在性能方面, C 代码之于 Go 就好比汇编代码之于 C.
2. 对 Go 内存 GC 的延迟敏感, 需要自己手动进行内存管理(分配和释放).
Expand Down Expand Up @@ -58,16 +61,15 @@ func main(){
1. C 代码直接出现在 Go 文件中, 但是是以注释的形式.
2. 在注释后我们导入了一个 C 包 `import "C"`.
3. `main` 函数中通过 C 包调用了 C 代码中定义的一个函数 `print`.
::: important
`import "C"` 和注释之间没有空行. 只有这样编译器才能够识别.
4.
::: `import "C"` 和注释之间没有空行. 只有这样编译器才能够识别.
:::

写完代码后应该对代码进行编译, 而对该文件的编译与常规并无不同:
```bash
go build -x -v how_cgo_works.go
```
::: note
`-x` `-v` 两个参数可以输出带有 Cgo 代码的 Go 文件编译细节.
::: note `-x` `-v` 两个参数可以输出带有 Cgo 代码的 Go 文件编译细节.
:::

实际编译时的主要操作:
Expand Down Expand Up @@ -168,19 +170,19 @@ go build -x -v how_cgo_works.go
如果想进行静态构建, 我们需要先将 C++ 库编译成二进制文件以供 Go 语言调用.

::: steps
1. 下载 Armadillo
1. 下载 Armadillo.

下载网址: [Armadillo官网](http://arma.sourceforge.net/)
下载网址: [Armadillo官网](http://arma.sourceforge.net/).

推荐使用 Stable Version.

2. 下载 Lapack 和 Blas 库
2. 下载 Lapack 和 Blas 库.

这两个库是对矩阵运算的优化, 如果想要 Armadillo 有更好的表现, 推荐用户下载安装.

3. Cmake 安装
3. Cmake 安装.

具体步骤可以参考 [Windows下利用 CMake 安装 Armadillo 库,包含 Lapack 和 Blas 支持库](https://blog.csdn.net/weixin_45847407/article/details/122275224)
具体步骤可以参考 [Windows下利用 CMake 安装 Armadillo 库,包含 Lapack 和 Blas 支持库](https://blog.csdn.net/weixin_45847407/article/details/122275224).

#### 代码
我们需要撰写两个部分的代码, 一个是 Go 语言空间的, 一个是 C 语言空间的.
Expand Down Expand Up @@ -283,25 +285,24 @@ extern "C" void logTransform(const double* data, double* result, int size) {

## 使用 Cgo 的开销

1. 调用开销
1. 调用开销.

benchmark 测试表明: 通过 Cgo 调用 C 函数付出的开销是调用 Go 函数的将近 30 倍.

2. 增加线程数量暴涨的可能性
2. 增加线程数量暴涨的可能性.

Go 以轻量级 goroutine 应对高并发而闻名, Go 会优化一些原本会导致线程阻塞的系统调用. 但是由于 Go 无法掌控 C 空间, 所以日常开发中容易在 C 空间写出导致线程阻塞的代码, 使得 Go 应用进程内线程数量暴涨的可能性增加. 这与 Go 承诺的轻量级并发有所背离.

3. 失去跨平台交叉构建能力
3. 失去跨平台交叉构建能力.

4. 其他开销
4. 其他开销.

- 内存管理. Go 空间采用垃圾回收机制, C 空间则采用手工内存管理.
- Go 所拥有的强大工具链在 C 中无法施展. 比如性能剖析工具, 测试覆盖率工具等.
- 调试困难.


::: danger
在 Cgo 的使用中必须注意内存的管理, 需要及时手动释放.
::: danger 在 Cgo 的使用中必须注意内存的管理, 需要及时手动释放.
:::


Expand Down
18 changes: 9 additions & 9 deletions docs/2. 编程语言/07.2023-09-28-Go 中的并发.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ func runTime() {

### 基于 Channel
channel 是 Go 中推荐使用的通信方式, 一个 channel 可以认为是一个线程安全的消息队列, 先进先出.
1. 语法
1. 语法.

[Go Channel详解](https://www.runoob.com/w3cnote/go-channel-intro.html)
2. 一些特殊情况
[Go Channel 详解](https://www.runoob.com/w3cnote/go-channel-intro.html)
2. 一些特殊情况.
- 向已经关闭的 channel 或为 nil 的 channel 中写, 会引发 panic.
- 从为 nil 的 channel 中读, 会永久阻塞.
- 从已经关闭的 channel 中读, 如果 channel 内已经没有数据了, 会返回相应零值, 可以用 `elem, ok := <-ch`, 使用 `ok` 来判断获取的值是不是有效值.

3. 非阻塞式收发
3. 非阻塞式收发.
正常使用 channel 进行数据的收发都是阻塞式的, 如果 channel 缓存已满, 再往里写就会阻塞. 如果 channel 中没有数据, 尝试读的话也会引起阻塞. 要实现非阻塞式的 channel 访问, 使用 `select`. `select` 是 Go 中一个特殊语法, 看起来和 `switch` 有点像.

```go
Expand Down Expand Up @@ -237,13 +237,13 @@ wg.Wait()
3. 利用用 `select` 语句.

## 系统中的一些应用
1. map-reduce
2. 几个任务流顺序执行
3. 递归中的并发数控制
1. map-reduce.
2. 几个任务流顺序执行.
3. 递归中的并发数控制.

## 性能分析工具
1. benchmark 基准测试
2. pprof
1. benchmark 基准测试.
2. pprof.


<br /><br /><br />
Expand Down
28 changes: 14 additions & 14 deletions docs/2. 编程语言/14.2024-08-13-Go 的 GC 原理图解.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ STW 的全称是: ***Stop the World***, 即在 GC 过程中的一些时刻我们
4. Start the World.

::: warning
非常明显, 算法最大的问题就是 GC 期间要把整个程序完全暂停. 在 Go 1.1版本中, STW 的时间可能达到秒级, 对于有高并发和吞吐量需求的系统是不能容忍的.
非常明显, 算法最大的问题就是 GC 期间要把整个程序完全暂停. 在 Go 1.1 版本中, STW 的时间可能达到秒级, 对于有高并发和吞吐量需求的系统是不能容忍的.

但是标记过程需要 STW 是因为, 如果在标记对象是否活跃期间如果程序未停止仍在运行, 则可能会出现引用关系在标记阶段做了修改的情况, 导致标记结果的不正确.
:::
Expand All @@ -85,15 +85,15 @@ STW 的全称是: ***Stop the World***, 即在 GC 过程中的一些时刻我们
::: steps
1. 步骤一

从灰色对象的集合中选择一个灰色对象并将其标记成黑色
从灰色对象的集合中选择一个灰色对象并将其标记成黑色.

2. 步骤二

将黑色对象指向的所有对象都标记成灰色, 保证该对象和被该对象引用的对象都不会被回收
将黑色对象指向的所有对象都标记成灰色, 保证该对象和被该对象引用的对象都不会被回收.

3. 步骤三

重复上述两个步骤直到对象图中不存在灰色对象
重复上述两个步骤直到对象图中不存在灰色对象.
:::

算法的伪代码如下:
Expand Down Expand Up @@ -146,9 +146,9 @@ while True:

在左侧的图体现了*强三色不变性*, 在右侧的图体现了*弱三色不变性*.

我们先来看左侧的强三色不变性. 考虑这样一种情况: 如果我们在Mark阶段不进行 STW, 则可能在标记的过程中程序运行时添加了一条 $A \rightarrow D$ 的指针( ==注意: 这次指针的添加**破坏**了强三色不变性== ). 但是此时 `A` 已经被执行 Mark 任务的 goroutine 所访问过并且已经标记成黑色, 按照三色标记的算法, 每次只会从灰色节点出发对灰色节点的引用进行访问和染色, 则意味着 `D` 不会被访问且在算法结束时 `D` 仍然会保持白色. 此时就会出现活跃对象 `A` 直接引用了 `D`, 但是 `D` 却被误认为了垃圾而被回收. 回收了仍然在被使用的对象会导致程序的致命错误, 是 GC 所不能接受的. 那么强三色不变性的意义就在于: 通过保证活跃节点(即黑色节点)到可能未访问的节点(即白色节点)的**直接路径**的有效性, 我们可以避免上述情况的发生.
我们先来看左侧的强三色不变性. 考虑这样一种情况: 如果我们在 Mark 阶段不进行 STW, 则可能在标记的过程中程序运行时添加了一条 $A \rightarrow D$ 的指针( ==注意: 这次指针的添加**破坏**了强三色不变性== ). 但是此时 `A` 已经被执行 Mark 任务的 goroutine 所访问过并且已经标记成黑色. 按照三色标记的算法, 每次只会从灰色节点出发对灰色节点的引用进行访问和染色, 则意味着 `D` 不会被访问且在算法结束时 `D` 仍然会保持白色. 此时就会出现活跃对象 `A` 直接引用了 `D`, 但是 `D` 却被误认为了垃圾而被回收. 回收了仍然在被使用的对象会导致程序的致命错误, 是 GC 所不能接受的. 那么强三色不变性的意义就在于: 通过保证活跃节点(即黑色节点)到可能未访问的节点(即白色节点)的**直接路径**的有效性, 我们可以避免上述情况的发生.

我们再来看右侧的弱三色不变性. 考虑这样一种情况: 我们在Mark阶段不进行 STW, 则可能在标记的过程中程序运行时添加了一条 $A \rightarrow D$ 的指针. 虽然打破了强三色不变性, 我们从黑色节点 `A` 无法访问到 `D`. 但是由于有一条从灰色 `B` 通往 `D` 的路线 $B \rightarrow E \rightarrow D$, 节点 `D` 仍然会被访问并染色, 不会造成 `D` 的错误回收. 所以弱三色不变性的意义在于: 通过保证黑色节点到白色节点的**间接路径**来保证 Mark 的正确性.
我们再来看右侧的弱三色不变性. 考虑这样一种情况: 我们在 Mark 阶段不进行 STW, 则可能在标记的过程中程序运行时添加了一条 $A \rightarrow D$ 的指针. 虽然打破了强三色不变性, 我们从黑色节点 `A` 无法访问到 `D`. 但是由于有一条从灰色 `B` 通往 `D` 的路线 $B \rightarrow E \rightarrow D$, 节点 `D` 仍然会被访问并染色, 不会造成 `D` 的错误回收. 所以弱三色不变性的意义在于: 通过保证黑色节点到白色节点的**间接路径**来保证 Mark 的正确性.

通过上面的两个例子我们看到, 遵循上述两个不变性中的任意一个都可以保证垃圾回收算法在不进行 STW, 也就是并发执行的场景下的正确性. 那么如果保证这两种不变性中的任意一种呢? 这就要涉及到接下来的一种技术: Write Barrier - 写屏障.

Expand Down Expand Up @@ -181,10 +181,10 @@ Dijkstra 的插入写屏障是一种相对保守的屏障技术, 它会将有存

为了弥补性能低的问题, Go 选择仅仅对堆上的指针插入写屏障(因为堆上一般放置的是大型对象、全局变量或者静态变量, 变动不频繁. 而栈上一般存放的是局部变量或参数, 会频繁变动). 但是这样会导致扫描结束后栈上仍然存在引用白色对象的现象. 所以为了保证算法的正确性, 此时进行 STW 然后重新扫描栈使其变黑. 这个过程就叫做 ***re-scan***. 此时整个算法的过程会变成:

1. 初始化 GC 任务: 包括开启写屏障和开启辅助 GC , 统计 root 对象的任务数量等. ==此过程 STW==.
2. 扫描所有 root 对象, 包括全局指针和 goroutine 栈上的指针(扫描某个 goroutine 时该 goroutine 停止), 将其加入灰色队列, 并循环灰色队列直至其为空. ==此过程后台并行执行==.
3. 重新扫描全局指针和栈(re-scan). ==此过程 STW==.
4. 按照标记回收所有白色对象. ==此过程后台并行执行==.
1. 初始化 GC 任务: 包括开启写屏障和开启辅助 GC, 统计 root 对象的任务数量等. <Badge>STW</Badge>
2. 扫描所有 root 对象, 包括全局指针和 goroutine 栈上的指针(扫描某个 goroutine 时该 goroutine 停止), 将其加入灰色队列, 并循环灰色队列直至其为空. <Badge>后台并行执行</Badge>
3. 重新扫描全局指针和栈(re-scan). <Badge>STW</Badge>
4. 按照标记回收所有白色对象. <Badge>后台并行执行</Badge>


### 删除写屏障
Expand All @@ -198,7 +198,7 @@ writePointer(slot, ptr)
```
上述代码会在老对象的引用被删除时, 将白色的老对象涂成灰色, 以保证弱三色不变性.

![yuasa删除屏障](/illustration/yuasa-delete-write-barrier.png)
![yuasa 删除屏障](/illustration/yuasa-delete-write-barrier.png)

1. 将根对象 `A` 标记为黑色, 将 `A` 指向的对象 `B` 标记为灰色.
2. 用户程序将 `A` 原本指向 `B` 的指针改为指向 `C`, 即删除了一个指针又新增了一个指针. 此时 ==触发删除写屏障==, 但是因为 `B` 本身是灰色所以不做任何改变.
Expand Down Expand Up @@ -227,13 +227,13 @@ Sweep 是清扫阶段, 会让 Go 知道哪些内存可以重新分配使用.
在 Sweep 过程中并不会去真正将内存清零(zeroing the memory), 而是在分配重新使用的时候重新 reset bit. GC 会使用位图 gcmarkBits 来跟踪正在使用的内存(1:存活; 2:回收), Sweep 只是对位图进行置 1 或置 0.

Go 提供两种策略:
1. 后台启动一个专门的worker等待清理内存.
2. 当申请分配内存时lazy触发.
1. 后台启动一个专门的 worker 等待清理内存.
2. 当申请分配内存时 lazy 触发.

## STW
Stop the World 是 GC 机制中一个重要的阶段, 当前运行的所有程序被暂停, 扫描内存的 root 节点并添加写屏障.

所有的处理器P都会被标记成停止状态(stopped), 不再运行任何代码. 调度器会把每个处理器M从各自队形的处理器P中分离出来放入 idle 列表中去.
所有的处理器 P 都会被标记成停止状态(stopped), 不再运行任何代码. 调度器会把每个处理器 M 从各自队形的处理器 P 中分离出来放入 idle 列表中去.

### Pacing
这是一个可配置的关于 GC 的参数. 表示下一次垃圾手机必须启动之前可以分配多少新内存的比例. 默认情况下为 100, 即下一次 GC 前可以分配 100% 的内存.
Expand Down
6 changes: 3 additions & 3 deletions docs/2. 编程语言/15.2024-09-29-堆和栈的区别.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ permalink: /article/fz5xcclm/
<!-- more -->

## 栈和堆存储在哪里?
它们都存储在计算机的RAM(随机存取存储器)中.
它们都存储在计算机的 RAM(随机存取存储器)中.

## 线程如何与栈和堆交互?多线程中栈和堆如何工作?
在多线程应用程序中, 每个线程都有自己的栈. 但是, 所有不同的线程将共享堆. 由于多线程应用程序中的不同线程共享堆, 这也意味着线程之间必须进行某种协调, 以便它们不会同时尝试访问和操作堆中的同一块内存.

## 对象可以存储在栈上而不是堆上吗?
是的, 对象可以存储在栈中. 如果您在函数内部创建对象而不使用`new`运算符, 那么这将在栈上创建并存储对象, 而不是在堆上. 假设我们有一个名为 `Member` 的 C++ 类, 我们想为其创建一个对象. 我们还有一个名为 `somefunction()` 的函数. 代码如下所示:
是的, 对象可以存储在栈中. 如果您在函数内部创建对象而不使用 `new` 运算符, 那么这将在栈上创建并存储对象, 而不是在堆上. 假设我们有一个名为 `Member` 的 C++ 类, 我们想为其创建一个对象. 我们还有一个名为 `somefunction()` 的函数. 代码如下所示:

```c++
void somefunction()
Expand All @@ -31,7 +31,7 @@ void somefunction()
} //the object "m" is destroyed once the function ends
```

因此, 一旦函数运行完成, 或者换句话说, 当它"超出范围"时, 对象`m`就会被销毁. 一旦函数运行完毕, 栈上用于对象`m`的内存将被删除.
因此, 一旦函数运行完成, 或者换句话说, 当它"超出范围"时, 对象 `m` 就会被销毁. 一旦函数运行完毕, 栈上用于对象 `m` 的内存将被删除.

如果我们想在函数内部的堆上创建一个对象, 那么代码将是这样的:
```c++
Expand Down

0 comments on commit 4f7fc8a

Please sign in to comment.