GO·NOTE

一份 Go 开发工程师的学习笔记

0%

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"runtime"
"sync"
)

func main() {

runtime.GOMAXPROCS(1)

var wg sync.WaitGroup
for i := 1; i <= 10; i++ {
wg.Add(1)
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}

wg.Wait()
}

结果

1
2
3
4
5
6
7
8
9
10
10
1
2
3
4
5
6
7
8
9

疑问

为什么不是 0-1-2-3-4-5-6-7-8-9-10 呢?为什么 10 会在最前面呢?

源码分析

源码地址:https://golang.org/src/runtime/proc.go?h=func%20runqput line=4690

1
2
3
4
5
6
7
8
9
10
11
12
if next {
retryNext:
oldnext := _p_.runnext
if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
goto retryNext
}
if oldnext == 0 {
return
}
// Kick the old runnext out to the regular run queue.
gp = oldnext.ptr()
}

这段代码的意思是 go 会把每个 P 所管理的最后一个 goroutine 放入 next 位置。为什么??

这是 go 设计认为或者是有过测试:如果一个 P 的 goroutine 队列在顺序执行的时候,因为 go sched 会有很多抢占或者调度。那么从被执行的概率上来分析的话,放入一个 next 位置可使得每个 goroutine 的执行概率是相当的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "syscall"

const LockFile = "singleton.lock"

func Singleton() {
if fd, err := syscall.Open(LockFile, syscall.O_CREAT|syscall.O_RDONLY, 0644); err != nil {
panic(err)
} else if err := syscall.Flock(fd, syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
panic(err)
}
return
}

系统调用命令大全

1
2
3
4
5
6
7
8
9
10
11
func (um *Model) InsertOrUpdate(session *xorm.Session) (int64, error) {

sql := fmt.Sprintf(`INSERT INTO %s (id,user_id,mine_id,status) VALUES(?,?,?,?)
ON CONFLICT (user_id,mine_id) DO UPDATE SET
status = ?;`, um.TableName())
result, err := session.SQL(sql, um.Id, um.UserId, um.MineId, um.Status).Execute()
if err != nil {
return -1, err
}
return result.RowsAffected()
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package check

import (
"errors"
"log"
"net/http"
"time"
)

func ApiCheck() {
if err := PingServer(); err != nil {
log.Fatal("The router has no response, or it might took too long to start up.")
}
log.Println("The router has been deployed successfully.")
}

func PingServer() error {
for i := 0; i < 10; i++ {
URL := "http://localhost:8080"
resp, err := http.Get(URL + "/health")
if err == nil && resp.StatusCode == 200 {
return nil
}
time.Sleep(time.Second)
}
return errors.New("cannot connect to the router")
}

LRU缓存淘汰算法

LRU是最近最少使用策略的缩写,是根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

双向链表实现LRU

将Cache的所有位置都用双链表连接起来,当一个位置被访问(get/put)之后,通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。

这样,在多次操作后,最近被访问(get/put)的,就会被向链表头方向移动,而没有访问的,向链表后方移动,链表尾则表示最近最少使用的Cache。

当达到缓存容量上限时,链表的最后位置就是最少被访问的Cache,我们只需要删除链表最后的Cache便可继续添加新的Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
type Node struct {
Key int
Value int
pre *Node
next *Node
}

type LRUCache struct {
limit int
HashMap map[int]*Node
head *Node
end *Node
}

func Constructor(capacity int) LRUCache{
lruCache := LRUCache{limit:capacity}
lruCache.HashMap = make(map[int]*Node, capacity)
return lruCache
}

func (l *LRUCache) Get(key int) int {
if v,ok:= l.HashMap[key];ok {
l.refreshNode(v)
return v.Value
}else {
return -1
}
}

func (l *LRUCache) Put(key int, value int) {
if v,ok := l.HashMap[key];!ok{
if len(l.HashMap) >= l.limit{
oldKey := l.removeNode(l.head)
delete(l.HashMap, oldKey)
}
node := Node{Key:key, Value:value}
l.addNode(&node)
l.HashMap[key] = &node
}else {
v.Value = value
l.refreshNode(v)
}
}

func (l *LRUCache) refreshNode(node *Node){
if node == l.end {
return
}
l.removeNode(node)
l.addNode(node)
}

func (l *LRUCache) removeNode(node *Node) int{
if node == l.end {
l.end = l.end.pre
}else if node == l.head {
l.head = l.head.next
}else {
node.pre.next = node.next
node.next.pre = node.pre
}
return node.Key
}

func (l *LRUCache) addNode(node *Node){
if l.end != nil {
l.end.next = node
node.pre = l.end
node.next = nil
}
l.end = node
if l.head == nil {
l.head = node
}
}

Go 语言没有 while 和 do…while 语法,可以通过 for 循环来实现其使用效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

func main() {
While()
DoWhile()
}

func While() {
i := 1
for {
fmt.Println("hello! ", i)
i++
if i > 10 {
break
}
}
}

func DoWhile() {
i := 1
for {
if i > 10 {
break
}
fmt.Println("hello! ", i)
i++

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

type Student struct {
Name string
Age string
}

func main() {
var sp *Student // 零值为 nil
var s Student // 不可与 nil 比较,零值为其中各个字段为 零值
s.Name = "xmge" // ok
sp.Name = "xmge" // error
}

//panic: runtime error: invalid memory address or nil pointer dereference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"os"
"github.com/urfave/cli"
)

func main() {
app := cli.NewApp()
app.Commands = []cli.Command{
{
Name: "test",
Usage: "test --uid=x --username=y",
Action: (&Command{}).Test,
Flags: []cli.Flag{
cli.IntFlag{Name: "uid",Usage:"--uid"},
cli.StringFlag{Name:"username",Usage:"--username"},
},
},
}
err := app.Run(os.Args)
if err != nil {
fmt.Print("command error :" + err.Error())
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"github.com/urfave/cli"
)
type Command struct {}

func (this *Command) Test(cli *cli.Context) {
uid := cli.Int("uid")
username := cli.String("username")
fmt.Println(uid,username)
}

package main

import (
“fmt”
“math/big”
)

func main() {
//设置一个大于int64的数
a := new(big.Int)
a, ok := a.SetString(“9122322238215458478512545454878168716584545412154785452142499999”, 10)
if !ok {
panic(“error”)
}
//String方法可以转换成字符串输出
fmt.Println(a.String())

//大数相加
b:=big.NewInt(2)
b=b.Add(a,b) //  Mod 取模、Add 加、Sub 减、Mul 乘、Div 除
fmt.Println(b.String())

}

应用场景

当我们进行浮点数进行(+ - * /)运算时,容易造成精度丢失,于是使用 big 包来处理。

使用步骤如下:

  1. 用 float64 变量构建 Big 对象。

  2. 通过调用 big 包的加,减,乘,除等相应的方法进行算术运算。

  3. 把BigDecimal对象转换成float,double,int等类型。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"math/big"
)

func main() {
//设置一个大于int64的数
a := new(big.Int)
a, ok := a.SetString("9122322238215458478512545454878168716584545412154785452142499999", 10)
if !ok {
panic("error")
}
//String方法可以转换成字符串输出
fmt.Println(a.String())

//大数相加
b := big.NewInt(2)
b = b.Add(a, b) // Mod 取模、Add 加、Sub 减、Mul 乘、Div 除
fmt.Println(b)

var c float64 = 9122322238215458478512545454878168716584545412154785452142499999
fmt.Printf("%v\n", c)
fmt.Println(c + 2)
}
1
2
3
4
9122322238215458478512545454878168716584545412154785452142499999
9122322238215458478512545454878168716584545412154785452142500001
9.122322238215458e+63
9.122322238215458e+63