Compare commits

..

7 Commits

Author SHA1 Message Date
徐涛
1a062e2140 post:完成Go的并发控制第二篇。 2022-09-28 13:44:20 +08:00
徐涛
17a7147d13 post:增加Go的Context使用文章。 2022-09-28 09:59:49 +08:00
徐涛
1bda27cca6 post:增加记录在ubuntu中安装nerdctl的文章。 2022-08-04 10:37:25 +08:00
徐涛
a48ff3432f post:增加Rust中关于使用类型选择的文章。 2022-08-04 09:45:36 +08:00
徐涛
e8f31beccb enhance:增加React工具书中的Zustand章节。 2022-06-20 09:51:14 +08:00
徐涛
835b08135f fix:补充React工具书内容。 2022-05-19 14:55:16 +08:00
徐涛
ed0482b5af fix:修改书名。 2022-05-17 16:28:12 +08:00
6 changed files with 697 additions and 2 deletions

View File

@@ -0,0 +1,106 @@
<mxfile host="65bd71144e">
<diagram id="Br-Qc5h3oGoIzwvD0Lxk" name="第 1 页">
<mxGraphModel dx="1725" dy="620" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="33" style="edgeStyle=none;html=1;startArrow=block;startFill=0;endArrow=none;endFill=0;" edge="1" parent="1" source="2" target="4">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="2" value="emptyCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="290" y="40" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="3" value="通过Background()获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="510" y="45" width="150" height="30" as="geometry"/>
</mxCell>
<mxCell id="24" style="edgeStyle=none;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;endArrow=none;endFill=0;startArrow=block;startFill=0;" parent="1" source="4" target="6" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="4" value="valueCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="290" y="120" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="5" value="通过WithValue(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="510" y="125" width="150" height="30" as="geometry"/>
</mxCell>
<mxCell id="25" style="edgeStyle=none;html=1;endArrow=none;endFill=0;startArrow=block;startFill=0;" parent="1" source="6" target="8" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="6" value="valueCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="290" y="200" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="7" value="通过WithValue(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="510" y="205" width="150" height="30" as="geometry"/>
</mxCell>
<mxCell id="26" style="edgeStyle=none;html=1;endArrow=none;endFill=0;startArrow=block;startFill=0;" parent="1" source="8" target="10" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="27" style="edgeStyle=none;html=1;endArrow=none;endFill=0;startArrow=block;startFill=0;" parent="1" source="8" target="12" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="8" value="valueCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="290" y="280" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="9" value="通过WithValue(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="510" y="285" width="150" height="30" as="geometry"/>
</mxCell>
<mxCell id="28" style="edgeStyle=none;html=1;endArrow=none;endFill=0;startArrow=block;startFill=0;" parent="1" source="10" target="18" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="10" value="cancelCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="190" y="360" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="11" value="通过WithCancel(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry y="365" width="160" height="30" as="geometry"/>
</mxCell>
<mxCell id="29" style="edgeStyle=none;html=1;endArrow=none;endFill=0;startArrow=block;startFill=0;" parent="1" source="12" target="14" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="12" value="timerCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="390" y="360" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="13" value="通过WithDeadline(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="600" y="365" width="170" height="30" as="geometry"/>
</mxCell>
<mxCell id="31" style="edgeStyle=none;html=1;endArrow=none;endFill=0;startArrow=block;startFill=0;" parent="1" source="14" target="16" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="14" value="valueCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="390" y="440" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="15" value="通过WithValue(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="610" y="445" width="150" height="30" as="geometry"/>
</mxCell>
<mxCell id="16" value="timerCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="390" y="520" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="17" value="通过WithTimeout(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="600" y="525" width="170" height="30" as="geometry"/>
</mxCell>
<mxCell id="30" style="edgeStyle=none;html=1;endArrow=none;endFill=0;startArrow=block;startFill=0;" parent="1" source="18" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="18" value="valueCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="190" y="440" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="19" value="通过WithValue(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry y="445" width="160" height="30" as="geometry"/>
</mxCell>
<mxCell id="32" style="edgeStyle=none;html=1;endArrow=none;endFill=0;startArrow=block;startFill=0;" parent="1" source="20" target="22" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="20" value="timerCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="190" y="520" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="21" value="通过WithDeadline(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="-10" y="525" width="170" height="30" as="geometry"/>
</mxCell>
<mxCell id="22" value="timerCtx" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="190" y="600" width="180" height="40" as="geometry"/>
</mxCell>
<mxCell id="23" value="通过WithTimeout(parent)获取" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry y="605" width="160" height="30" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,247 @@
---
title: 小记Go里常用的并发控制手段
tags:
- Go
- Golang
- 并发控制
- Context
- Sync
categories:
- - Go
- 应用技巧
keywords: 'go,并发,context,sync,控制'
date: 2022-09-28 09:58:56
---
并发编程是Go语言无可置疑的强项之一为了解决并发编程中常见的一些控制需求Go也同样在标准库中提供非常常用的控制手段。而且这些控制手段在日常进行Go语言编程的时候也非常的常见。<!--more-->
常用的控制功能主要是`context`包中提供的各种`Context``sync`包中提供的各种同步原语。
## 面向上下文的编程
`Context`直译过来就是上下文上下文的使用在Go中也是一种编程模式。在并发编程中处理超时、取消等操作是比较常见的一般的处理方式是在这些异常情况出现的时候进行抢占操作或者中断操作。俩要解决这个问题我们首先想到的可能是使用`channel`,例如以下这个示例。
```go
func main() {
messageChannel := make(chan int, 10)
done := make(chan bool)
defer close(messageChannel)
ticker := time.NewTicker(1 * time.Second)
for range ticker.C {
select {
case <-done:
fmt.Println("interrupt...")
return
default:
fmt.Printf("message: %d\n", <-messageChannel)
}
}
// 这里可以向messageChannel中推送一些内容
close(done)
}
```
这段代码其实非常容易理解,往名称为`done`的这个`channel`发送任意的一个数据(包括关闭这个`channel`就会使协程优雅的退出。在这种模式下每控制一个协程就需要定义一个buffer为0的`channel`,如果协程变多了以后,那么需要控制的`channel`也就变的复杂不容易控制了。而且如果各个协程还需要一些超时控制、取消控制,那么可能在程序所花费的大部分精力就都集中在控制上了。
在实际的项目中常常有这样一种情况一个协程例如名称为R1存在有若干的子任务例如名称为T1、T2、T3等等协程R1对其下的子任务是带有超时控制的而子任务T1、T2等对其下的第二级子任务ST1、ST2等也同样具有超时控制。那么在这种情况下ST1、ST2这些第二级子任务除了需要感知协程R1的取消信号也同样需要感知其归属子任务T1、T2这些第一级子任务的取消信号。如果还是采用之前基于`channel`的控制方案,那么就会出现`channel`的嵌套,整个控制逻辑就会变得非常复杂,很容易混乱,如果子任务的层级再多一些,程序的代码基本上就会进入到无法维护的境地了。
对于这种情况,最优雅的解决方案是下面这样的:
1. 上级任务被取消以后,其下的所有下级任务都会被取消。
1. 中间某一个层级的任务被取消以后,不会影响其上级的和其同级的任务。
Go为了实现这种优雅的控制方法就引入了`Context`。标准库中的`Context`是一个接口,其中包含了四个方法,以下是`Context`接口的源码,可以看看其中各个方法的用途。
```go
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) inteface{}
}
```
从直觉上看起来这个`Context`的结构有一种很熟悉的感觉,好像看起来也是一个类似于上面基于`channel`的控制结构。其实原理是差不多的。
- `Deadline()`会返回当前`context`被取消的截止时间,如果`context`中没有设置之歌截止时限,那么返回的`ok`将是`false`
- `Done()`会返回一个用于表示当前`context`是否已经被取消的状态。如果`context`被取消,那么`Done()`将返回一个关闭的`channel`;如果`context`不会被取消,那么将会返回`nil`
- `Err()`方法会配合`Done()`返回相应的内容来表示`context`的状态。如果`Done()`返回的`channel`没有被关闭,那么`Err()`将会返回`nil`;如果`channel`已经关闭了,那么`Err()`将会返回非空的值表示任务结束的原因;如果`context`被取消,那么`Err()`将会返回`Canceled`;如果`context`超时,那么`Err()`将会返回`DeadlineExceeded`
- `Value()`用于返回在`context`存储的键值对中的指定键对应的值,如果找不到指定的键,那么就会返回`nil`
从这四个方法可以得出,在并发任务中`Context`要怎样来使用。
1. `Done()`方法返回的`channel`是用来传递结束信号来抢占并中断当前的任务,相当于之前控制方法中名为`done``channel`
1. `Deadline()`用来指示一段时间以后,`context`是否会被取消。
1. `Err()`用来解释`context`的取消原因。
1. `Context`会形成一棵`Context`树,其中每一个节点都会携带一些键值对,如果`Value()`方法在当前的节点中找不到指定的键,那么就会到上一级节点中继续查找,直到根节点为止。
`Context`来重新实现上面的示例,就可以让控制过程变得比较清晰了。
```go
func main() {
messageChannel := make(chan int, 10)
// 这里可以向messageChannel中推送一些内容
// 设定5秒钟以后结束context
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
go func(ctx context.Context) {
ticker := timer.NewTicker(1 * time.Second)
for range ticker.C {
select {
case <-ctx.Done():
fmt.Println("interrupt...")
return
default:
fmt.Printf("message: %d\n", <-messageChannel)
}
}
}(ctx)
defer close(messageChannel)
select {
case <-ctx.Done():
fmt.Println("main process exit...")
}
}
```
在这个示例中虽然只启动了一个协程,但是可以看出来,这个协程的控制是通过传入的`context`控制的。如果同时启动了多个协程,那么只要使用了相同的`Context`实例,那么这些协程就可以统一的被这一个`Context`实例所控制。如此以来,就可以简化协程的控制了。而且前面也提到,`Context`是可以形成一棵树的,当其中的一个节点变成了取消状态,那么这个取消状态很容易就可以被传递到其子代的节点上,这样也就实现了对子代协程的控制。
## 常用的`Context`类型
从前面的`Context`源码可以看出来,`Context`只是标准库中规定的一个接口,并不是实际使用中的`Context`实现。其实在Go的标准库中已经提供了一些常用的`Context`实现,但是这些实现的共同特点就是不能被显式实例化,只能使用一系列`context`包中提供的方法来获取。
### 基础`Context`类型
标准库中所提供的基础`Context`类型主要有四个,分别是`emptyCtx``valueCtx``cancelCtx``timerCtx`。它们分别被用作执行不同控制功能的`Context`使用。
#### `emptyCtx`
`emptyCtx`是所有的`Context`类型的基础,它的定义非常简单:
```go
type emptyCtx int
```
所以`emptyCtx`就是一个`int`类型的别名,但是实现了`Context`接口所需要的全部方法。`emptyCtx`没有超时时间,不能被取消,也不能存储任何键值对信息,它的目的只有一个:作为`Context`树的根节点。
!!! warning ""
不要尝试直接创建`emptyCtx`的实例在Go中这种首字母为小写的内容都是私有的标准库就是利用这个规则来阻止你直接实例化所有的`Context`实例。
#### `valueCtx`
`valueCtx`就比前面的`emptyCtx`要复杂多了。在它里面可以保存一对键值对信息,它的定义为:
```go
type valueCtx struct {
Context
key, val interface{}
}
```
`emptyCtx`不一样,`valueCtx`就已经是一个结构体了。`valueCtx`中利用`Context`类型的字段来记录其父节点,所以`valueCtx`也就继承了其父级`Context`的所有信息。`key``val`两个字段表示一个键和值都是任意类型的键值对。
看到这里可能会有疑问,`valueCtx`中只能保存一个键值对信息,那么如果要在`Context`中保存多个键值对要怎么办。其实在`Context`的标准库实现中,多个键值对信息是靠一个`Context`链来保存的。这里只需要看一下`valueCtx`实现的`Value()`方法就明白了。
```go
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
```
`valueCtx``Value()`方法的最后,实例返回了其父级`Context``Value()`方法的值,所以在当前的`Context`实例中如果找不到指定`key`的值,`Context`实例会自动的沿着`Context`链向上寻找。
!!! note ""
如果一直到根节点都不能找到指定`key`,那么就会返回根节点实现的`Value()`方法返回的值,也就是`nil`
#### `cancelCtx`
`cancelCtx`就更加复杂了一些,其中主要是增加了用于表示和控制任务取消的功能。它的定义为:
```go
type cancelCtx struct {
Context
mu sync.Mutex
done chan struct{}
children map[canceler]struct{}
err error
}
type canceler struct {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
```
`cancelCtx`中,依旧与`valueCtx`一样,使用`Context`字段来保存父级`Context`,但是多了一个通道类型的`done`字段来传递取消信号。`chidren`字段是`cancelCtx`用来记录其子`Context`的,当调用`cancelCtx`中的`cancel()`方法的时候,实际上会迭代的调用`children`字段中所有子`Context``cancel()`方法。
#### `timerCtx`
`timerCtx`是基于`cancelCtx`构建的类型,相比`cancelCtx``timerCtx`只是增加了可以定时执行取消的功能。所以它的定义为:
```go
type timerCtx struct {
cancelCtx
timer *time.Timer
deadline time.Time
}
```
`timerCtx`内部,`Context`的取消还是通过内部的`cancelCtx`来完成,但是取消的激活是靠`timer``deadline`字段来完成的。
### `Background()`和`TODO()`
标准库中提供的`Background()``TODO()`两个方法是用来获取根`Context`节点的,因为`emptyCtx`不能被手动实例化,所以在使用的时候就需要标准库中提供的这两个方法。从代码上看,`Background()``TODO()`返回的内容是一样的。
```go
var(
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
```
但是根据标准库中的解释,这两个`Context`实例的功用还是不一样的。`Background()`通常都是被用在主函数中的,所以`Background()`是我们程序中常用的根`Context`节点;而`TODO()`则是在不知道需要使用什么`Context`实例的时候才用得到。
### `WithValue()`
`WithValue()`方法是用来根据指定的父级`Context`创建一个携带着键值对内容的`Context`节点的,也是向`Context`中记录内容的唯一方法。
### `WithCancel()`
`WithCancel()`方法是用来根据给定的父级`Context`创建一个可以被取消的`Context`节点,其在调用以后会返回两个值:一个是新创建的`Context`节点,一个是用于触发取消的`CancelFunc`。要取消这个`Context`只需要调用创建的时候返回的`CancelFunc`即可。
### `WithTimeout()`和`WithDeadline()`
`WithTimeout()``WithDealine()`都是会返回一个`timerCtx`的实例,但是同时也都会返回一个用于手动触发取消动作的`CancelFunc`。这两个方法所返回的`timerCtx`不一样的地方在于所设置的取消时间的设定,`WithDeadline()`设置的是一个过期的时间点;`WithTimeout()`设置的是一个相对于当前时间的过期时长。
## `Context`树的形成
在标准库中每一个`Context`类型都会保存其父级`Context`实例,而所有用来获取`Context`的方法也都必须要提供一个父级`Context`,这样就会形成一个树。就像下面这张图所描述的那样。
{% oss_image concurrent-control-in-go/go-context-tree.svg Context树的形成 700 %}
每调用一次获取`Context`实例的方法,就会创建一个新的节点,然而对于可以激活取消的`Context`节点,一旦触发其取消方法,那么就会将其连同其下的所有子代节点都取消掉。这就是`Context`树真正的使用目的。
!!! note "一些额外的用法"
根据`Context`中提供的保存键值对的功能,可以利用其在整个程序中共享一些全局内容,比如数据库连接、公共配置等。所以需要尽可能的在程序的各个角落都使用`Context`

View File

@@ -0,0 +1,163 @@
---
title: 小记Go里常用的并发控制手段
tags:
- Go
- Golang
- 并发控制
- Mutex
- Lock
- Sync
- WaitGroup
- Cond
categories:
- - Go
- 应用技巧
keywords: 'go,并发,mutex,lock,sync,控制,cond,waitgroup'
date: 2022-09-28 13:43:51
---
传统的并发控制在Go的标准库中也是有提供的而且使用起来也非常简单。但是需要注意的是这些传统的控制手段在使用的时候同样会面临传统并发编程中会遇到的所有挑战。<!--more-->对于这些传统的并发控制手段,主要都是由标准库中的`sync`包提供的。`sync`包中提供的主要类型有`Locker``Cond``Map``Mutex``Once``Pool``RWMutex``WaitGroup`。其中可能有不少类型的名字听起来都十分的熟悉,这里只选其中比较常用的几个来记录。
## `Locker`
`Locker``sync`包中提供的一个接口,主要用来表示提供了`Lock()``Unlock()`方法的可以用来在并发编程中执行锁的功能的对象,例如`sync`包中提供的`Mutex`类型、`RWmutex`类型等。
## `Once`
`Once`是一个可以被多次调用但是只会执行一次的对象,不论每次传入的参数是否相同,`Once`就是只会执行一次。例如以下这个示例。
```go
func main() {
var once sync.Once
fnBody := func() {
// 执行一些只需要执行一次的方法
}
for i := 0; i < 10; i++ {
go func() {
once.Do(fnBody)
}()
}
// 继续其他的功能
}
```
!!! note ""
`Once`对象也适合用来创建单例对象使用。
## `Mutex`与`RWMutex`
`Mutex`是经典的互斥锁实现,互斥锁在刚被创建的时候是未锁闭状态,而且在使用的时候,一定注意要使用引用的形式(指针)传递互斥锁的实例,否则互斥锁将失去其并发控制功能。`Mutex`类型实现了`Locker`接口,其提供的方法主要可以实现以下功能。
- `Lock()`,这是`Locker`接口规定必须要实现的功能,当调用`Lock()`的时候,会尝试获取锁并锁闭,如果不能成功获取到锁并锁闭,那么将会阻塞当前的协程直到成功获取到锁为止。
- `TryLock()`,用于尝试获取锁,但是在未获取到锁的时候,并不会阻塞当前的协程,而是会发回一个`false`返回值。
- `Unlock()`,解锁并释放当前获取到的锁。互斥锁被解锁并释放以后,就可以被其他的协程所获取并锁闭。
在大部分并发编程中,并不推荐直接使用`Mutex`这种偏底层的并发控制。
`Mutex`类型引申出来的类型是`RWMutex`,即读写锁,主要用于单写多读的情况下。因为大部分的资源在并发条件下,只有写操作是互斥的(影响值的一致性),但是读操作是不互斥的。所以读写锁提供了写锁和读锁两种锁,对应的也提供了两种锁的操作方法。
- `Lock()``Unlock()`用于对写操作进行加锁和解锁,调用`Lock()`会导致读和写都被锁闭。
- `RLock()``RUnlock()`用于对读操作进行加锁和解锁,调用`RLock()`会导致写操作被锁闭,但是不会影响其他的协程继续调用`RLock()``RUnlock()`只会解锁一次`RLock()`,不会影响其他剩余的`RLock()`调用。资源必须在所有的`RLock()`都被解锁以后,才能进行写操作。
!!! warning ""
如果调用`RUnlock()`的次数多于调用`RLock()`的次数程序将会panic。如果多次锁闭但是不进行解锁将会导致死锁所以在使用的时候锁闭与解锁的操作必须是对称的并且是可以抵达的。
以下是`RWMutex`的一个简单使用示例。
```go
var (
rwm sync.RWMutex
val int
)
for i := 0; i < 10; i++ {
go func() {
rwm.RLock()
fmt.Printf("%d\n", val)
rwm.RUnlock()
time.Sleep(rand.Int(20) * time.Second)
}()
}
for i := 0; i <= 60; i++ {
rwm.Lock()
val = i
rwm.Unlock()
time.Sleep(1 * time.Second)
}
```
## `Cond`
`Cond`是创建一个条件变量来对协程进行控制,所有受控的协程都会集结在这个条件变量的位置上等待调度。举例来说就是多个协程在等待,一个协程在发送通知事件,这种情况通常出现在多个协程在等待资源准备就绪的场景中,
`Cond`的核心是一个`Locker`类型的字段,这个`Locker`类型的字段被用来在各个协程中的完成控制操作,这个`Locker`类型的字段通常都是使用`Mutex`类型或者`RWMutex`类型的实例来充当。`Cond`提供了以下几个方法来进行协程的控制。
- `Broadcast()`用于唤醒所有等待`Cond`变量的协程。
- `Signal()`用于只唤醒一个正在等待`Cond`变量的协程。
- `Wait()`用于挂起调用协程,让调用`Wait()`的协程等待`Broadcast()`或者`Signal()`
!!! warning ""
在协程中调用`Wait()`挂起当前协程的时候,需要先调用`Cond`实例中`Locker`对象的锁,这个锁是用来锁闭协程中所使用到的共享资源的。也就是说在使用`Wait()`之前,必须先锁闭`Cond`实例。
!!! note ""
不必担心调用`Wait()`的时候`Cond`中的锁会阻止通知协程对于共享资源的访问。在调用`Wait()`的时候,`Cond`中的所将会自动解锁,当`Wait()``Broadcast()`或者`Signal()`结束而返回的时候,会自动再次对`Cond`中的锁进行锁闭。
以下是一个使用`Cond`进行协程控制的简单示例。
```go
var (
m sync.Mutex
c = sync.NewCond(&m)
sharedResouce = false
)
for i := 0; i < 4; i++ {
go func() {
c.L.Lock() // 锁闭Cond的锁用来访问共享的sharedResouces
for sharedResource == false {
// 完成共享资源就绪前的处理
c.Wait()
}
// 完成资源就绪以后的处理
c.L.Unlock()
}()
}
go func() {
time.Sleep(rand.Int(100) * time.Second)
c.L.Lock()
sharedResources = true
c.Broadcast()
c.L.Unlock()
}()
```
## `WaitGroup`
`WaitGroup`直译过来就是“等待组”,它的功能就是用来等待一组协程的结束。`WaitGroup`的使用其实很简单,只需要在主协程启动子协程之前调用`Add()`来调整目前需要等待的子协程数量,然后在子协程执行结束以后调用`Done()`,然后再在需要让主协程停下来的地方调用`Wait()`即可。用一个示例说明就是下面这样。
```go
func main() {
var wg sync.WaitGroup
// 初始化需要完成的任务tasks
for _, task := range tasks {
wg.Add(1) // 调整等待组中需要等待任务的数量
go func() {
defer wg.Done() // 标记等待组中任务的完成
// 执行task
}()
}
wg.Wait() // 暂停主协程,使其等待子协程的完成
}
```

View File

@@ -0,0 +1,90 @@
---
title: 在Ubuntu LTS中启用CGroup v2并安装nerdctl
tags:
- Linux
- 容器化
- Ubuntu
- systemd
- cgroup
categories:
- - DevOps
- 系统管理
keywords: 'ubuntu,containerd,nerdctl,cgroup'
date: 2022-08-04 10:36:50
---
如果云主机采用了Ubuntu 21.04或以下的版本时默认开启的是CGroup v1如果要使容器系统能够使用CGroup v2来管理资源就必须手动开启CGroup v2。本文将记录如何在Ubuntu 21.04及以下系统中开启CGroup v2并安装containerd容器系统和nerdctl容器控制工具的过程。<!-- more -->
## 启动CGroup v2功能
在启动系统的CGroup v2功能之前首先需要确认系统是否提供了CGroup v2的支持并且系统是否已经启动了CGroup v2功能。
在命令行中执行以下命令可以查看系统是否提供了CGroup v2的支持。
```bash
grep cgroup /proc/filesystems
```
如果输出的结果中包含以下内容那就说明系统已经提供了CGroup v2的支持。
```bash
nodev cgroup2
```
接下来是检查系统中是否已经激活了CGroup v2。同样在命令行中执行以下命令
```bash
ls /sys/fs/cgroup/cgroup.controllers
```
如果系统提示找不到指定文件那么就说明系统目前没有激活CGroup v2。
此时就可以激活系统中的CGroup v2了需要注意的是激活系统中的CGroup v2功能会要求系统重新启动如果正在操作生产系统要保证当前系统管理操作不会对系统中所承载的服务产生影响。
打开系统中的`/etc/default/grub/`文件,在其中的`GRUB_CMDLINE_LINUX`参数的尾部增加以下内容。
```text
systemd.unified_cgroup_hierarchy=1
```
添加完以后的`GRUB_CMDLINE_LINUX`参数可能是这样的:
```text
GRUB_CMDLINE_LINUX=" vga=792 console=tty0 console=ttyS0,115200n8 net.ifnames=0 noibrs systemd.unified_cgroup_hierarchy=1"
```
完成添加以后执行以下两条命令,更新系统引导并重新启动系统。
```bash
sudo update-grub
sudo shutdown -r now
```
!!! note ""
如果是使用root用户进行的操作则不必使用`sudo`
完成系统重启以后可以再次执行检查CGroup v2是否已经激活的命令此时应该会列出`cgroup.controllers`文件。
!!! note ""
执行命令`cat /sys/fs/cgroup/cgroup.controllers`可以检查目前有哪些设备已经托管给了CGroup。
## 安装containerd和nerdctl
containerd是一个OCI标准的容器化支持环境但是并不需要我们单独的去完成containerd的安装。nerdctl的完整安装包内已经集成了containerd、runC和CNI所以直接在Ubuntu中安装nerdctl的完整版即可。
顺序执行以下命令可以下载nerdctl 0.22.2版并将nerdctl和containerd等都安装到`/usr/local`中。
```bash
wget https://ghproxy.com/https://github.com/containerd/nerdctl/releases/download/v0.22.2/nerdctl-full-0.22.2-linux-amd64.tar.gz
tar Cxzvf /usr/local nerdctl-full-0.19.0-linux-amd64.tar.gz
cp /usr/local/lib/systemd/system/*.service /etc/systemd/system/
systemctl enable buildkit containerd
systemctl start buildkit containerd
```
以上命令的最后两行是启动containerd和镜像打包支持。安装包解压完毕以后不必重启系统应该就已经可以执行`nerdctl`命令了。
如果当前不能使用root用户来完成containerd和nerdctl的安装那么就需要使用Rootless方式安装nerdctl了。这种情况下可以执行完上面第二条解压命令以后执行以下命令完成安装。
```bash
containerd-rootless-setuptool.sh install
```

View File

@@ -0,0 +1,81 @@
---
title: 自有类型与借用类型
tags:
- Rust
- 类型系统
- 所有权
- 借用
- 引用
categories:
- - Rust
- 语言基础
keywords: 'rust,类型系统,自有类型,借用类型'
date: 2022-07-04 11:03:34
---
在根据Rust的所有权系统使用变量的时候值通常会发生移动来改变其拥有者这通常总是适用于Rust自有类型但是Rust中的借用类型的表现就不一样了因为在这些类型中存放的实际上是另一个内存区域的地址而借用类型的变量一般并不拥有它所指向的那一片内存区域。这种行为上的区别就构成了Rust中两种不同的类型自有类型和借用类型。<!--more-->
## 自有类型
自有类型其实比较好理解这种类型是完全遵守Rust中的所有权机制的。自有类型变量所拥有的内存区域中保存的就是实实在在的值。如果发生转移那么这一块内存区域的所有权就将被直接转移到新的变量名下。
```rust
fn main() {
let person1: String = String::from("John");
// 使用赋值语句将会使person1所拥有的内存区域转移给person_alias。
let person_alias = person1;
// 发生转移以后再使用person1就会失败。
println!("Person name: {}", person1);
}
```
在程序中使用自有类型是不会有任何歧义的因为任何操作都必须遵循Rust的所有权机制。但是使用自有类型会对编程过程中的自由性产生一定的影响。
## 借用类型
Rust中的借用类型就不是那么遵守所有权机制了在代码中借用类型一般都非常好辨认在借用类型的前面通常都会带着一个`&`符号。当然这种类型在其他语言中通常被称作引用类型。
同样是上面的示例,采用借用类型以后,运行效果就会变得不一样。
```rust
fn main() {
let person1: &String = &String::from("John");
// 这里同样使用一个赋值语句但是需要注意的是person_alias现在的类型也是&Stirng
let person_alias = person1;
println!("Person alias: {}", person_alias);
// 这会儿再使用person1的时候并不会出现任何错误。
println!("Person name: {}", person1);
}
```
就像是之前提到过的,借用类型变量所直接拥有的内存区域中保存的实际上是另一块内存区域的地址,程序中所需要使用的值实际上是被放置在这一块内存区域中的,借用类型变量是通过指针指向来访问所需要使用的值的。
在上面的这个示例中,赋值语句的是使用并没有发生任何的所有权转移。这也就是借用类型并不是那么遵守所有权机制的原因。
## 如何选择使用
在了解自有类型和借用类型的区别以后,就能理解为什么从函数中不能返回借用类型的原因了,因为从函数中返回值通常是所有权的转移,但是借用类型对于其所指向的目标内存区域是没有所有权的。所以即便是在函数中使用了借用类型,在返回其指向的值的时候,也应该借助于`.to_owned()`方法将其转换为自有类型,但是要谨记在调用函数的地方还是要处理所有权的。
不管在任何时候,使用自有类型作为函数的参数类型总不是一个好的选择。函数实参的所有权会被转移到函数中,如果这一段内训区域在使用完以后没有把所有权转移回来,那么在调用完函数以后,用作函数实参的变量就不能再用了。所以在选择函数参数类型的时候,应该首选借用类型。
另外,选择可切片类型和胖指针类型作为函数参数类型,还可以避免类型带有强制隐式类型转换带来的代码复杂度。例如传递字符串的时候使用`&str`而不是`&String`,传递数组使用`&[T]`而不是`&Vec<T>`,选择`&T`代替`&Box<T>`。例如有函数`fn str_length(word: &String): i32`,在传入一个引用的时候,是可以正常运行的,例如`str_length(&person_name)`,但是直接传入一个字符串字面量就不行了,例如`str_length("John")`。这是因为在调用函数的时候,表示字符串字面量的`&str`类型不能被强制转换成函数参数所要求的`&String`类型。但是如果将函数定义为`fn str_length(word: &str): i32`,那么就可以避免这个强制类型转换,而解决这个问题。
### 隐式类型转换规则
在Rust中发生这种强制隐式类型转换是有一定规则可循的一般情况下强制隐式类型转换的目的都是弱化指针或者解引用例如常见的有以下形式。
1. 弱化指针:
- `&mut T`转换为`&T`
- `*mut T`转换为`*const T`
- `&T`转换为`*const T`
- `&mut T`转换为`*mut T`
1. 解引用:
- 如果`T: Deref<U>`,那么可以将`&T`通过表达式`&*x`转换为`&U`
这种隐式类型转换通常会发生在`let``const``static`、函数调用传参、从函数返回值等位置。

View File

@@ -19,12 +19,20 @@ date: 2021-07-12 14:13:26
| 首次发布 | 最近更新 | 修订版本 | 适用 React 版本 |
| :-----------: | :-----------: | :------: | :-------------: |
| 2019 年 09 月 | 2022 年 05 月 | 47 | 18.1.0 |
| 2019 年 09 月 | 2022 年 06 月 | 49 | 18.1.0 |
点此下载:{% oss_link react-books/React.pdf React技术栈速查手册 %}
点此下载:{% oss_link react-books/React.pdf "《Live with React》" %}
## 更新记录
修订版本 49:
- 增加Zustand状态管理库的使用说明。
修订版本 48:
- 补充使用Redux Toolkit时在一个State片段中响应其他State片段定义的Action的说明。
修订版本 47:
本次手册内容的修订包含一次React大版本的更新本次更新引入了突破性变更的内容。