gobyeaxmple

介绍

并发编程 异步模型

1、seamless轻量级跨核心抢占式

2、csp and shared by communicating

goroutines比线程切换快10倍,运行时增长栈,消除同步与异步代码之间区别

使用语言内建的通道消除锁需求

安装:

rm -rf /usr/local && tar -xvf /usr/local xxx

go mod ini:初始化你的代码模块,通常为保存源代码的存储库路径

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("Hello, World!")
}

跟py挺像的,导入fmt包,也可以通过go mod tidy导入别人的包

import "rsc.io/quote",也可以通过go build生成二进制文件

1
2
3
4
5
6
package main
import "fmt"
import "rsc.io/quote"
func main() {
fmt.Println(quote.Go())
}

变量

var声明变量,go推断初始化变量的类型

未初始化为0,:=是声明和初始化的简写

1
2
3
var s = "initial"
var b,c int = 1, 2
f := "apple"

const

A const statement can appear anywhere a var statement can.

常量没有类型,通过显示转换或函数调用赋予类型;例如math.Sin需要一个float64

1
2
3
4
5
6
7
8
9
10
11
package main
import (
"fmt"
"math"
)
func main(){
const n = 500000000
const d = 3e20 / n

fmt.Println(math.Sin(n))
}

for

go中唯一的循环,一下简单的三种情况

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import "fmt"
func main(){
for i := 0; i < 3; i++{
fmt.Println(i)
}
for i := range 3{
fmt.Println(i)
}
for{
break
}
}

if/else

;之前声明语句,后续分支都可以使用

1
2
3
4
5
6
7
if num := 9; num < 0 {

} else if num < 10 {

} else {

}

switch/case

这里whatAmI类似c++的函数模板,interface{}接口值用来发现类型

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
package main
import(
"fmt"
"time"
)

func main(){
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
fmt.Println("Its the weekend")
default:
fmt.Println("Its a weekday")
}

t := time.Now()
switch{
case t.Hour() < 12:
fmt.Println("Its before noon")
default:
fmt.Println("Its after noon")
}
whatAmI := func(i interface{}){
switch t := i.(type){
case bool:
fmt.Println("I am a bool")
case int:
fmt.Println("I am an int")
default:
fmt.Printf("Don't know type %T\n",t)
}
}
whatAmI(true)
WhatAmI(1)
whatAmA("hey")
}

array

默认情况数组为零值var a [5]int

使用…省略长度b = [...]int{1,2,3,4,5}

使用:指定索引,期间元素被清零c = [...]{100, 3: 400}

多维数组:twoD = [2][3]int{ {1,2,3}, {4,5,6} }

slices

这里切片同py的一样有强大功能

通过包含的元素而不是元素数量来确定类型,未初始化为nil,长度为0,通过make构造切片(同数组一样)

切片长度可变;append添加

1
2
3
4
5
6
7
8
twoD := make([][]int 3)
for i := 0; i < 3; i++{
innerLen := i+1
twoD[i] = make([]int, innerLen)
for j := 0; j < innerLen; j++{
twoD[i][j] = i+j
}
}

map

通过_空白标识符消除0值,prs判断键值是否存在

1
2
3
4
5
6
m := make(ma[stirng]int)
m["k1"] = 7
m["k2"] = 13
delete(m,"k2")
_, prs := m["k2"]
n := ma[string]int{"foo":1, "bar":2}

ranges

ranges在数组中提供索引和值的访问,依然是通过_忽略

函数

可以只声明最后一个参数的类型

多值返回:func vals() (int,int){ return 3,7 }

变参函数:比如传递数组或切片 func sum(nums...int){}

闭包:多次调用闭包,函数隐藏i变量以形成闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func intSeq func() int {
i := 0
return func() int{
i++
return i
}
}

func main() {
nextInt := intSqe()
fmt.Println(nextInt())
fmt.Println(nextInt())
}

递归

1
2
3
4
5
6
func fact(n int) int {
if n == 0 {
return 1
}
return n * fact(n-1)
}

指针

var iptr *int

结构体

type person struct {}可以安全的返回指向局部变量的指针,

使用&person生成结构体指针,结构体是可变的

方法

这里的方法看起来跟类成员方法一模一样,还省去了定义和创建类步骤

1
2
3
4
5
6
7
8
9
type rect struct{
width,height int
}
func (r* rect) area() int{
return r.width * r.height
}
func main() {
r.area()
}

接口

gometry接口像是函数路由一样,分发到重写过的函数去

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
package main
import (
"fmt"
"math"
)

type gometry interface {
area() float64
perim() float64
}
type rect struct{
width, height float64
}
type circle strcut{
readius float64
}
func (r rect) area() float64{
return r.width * r.height
}
func (r rect) perim() float64{
return 2*r.width + w*r.height
}
func (c circle) area() float64{
return matn.Pi * c.radius
}
func (c circle) perim() float64{
return 2 * math.Pi * c.radius
}
func measure(g geometry){
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main(){
r := rect{3,4}
c := circle{5}

measure(r)
measure(c)
}

embedding

strcut嵌入struct,必须显示初始化,可以直接访问base的字段co.num,感觉像是继承一样

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 main
import "fmt"
type base struct{
num int
}
func (b base) describe() string{
return fmt.Sprintf("base with num=%v", b.num)
}
type container struct {
base
str string
}
func main(){
co := container{
base: base{1},
str: "some name",
}
fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str)
fmt.Println("also num:", co.base.num)
fmt.Println("describe:",co.describe())

type describer interface {
describe() string
}
var d describer = co
fmt.Println("describer:", d.describe())
}

泛型

泛型类似模板,以下几个应用例子

MapKeys是接受任意类型的Map返回Key切片的泛型函数,被调用时不需要指定类型,会自动类型推断

通过泛型是实现了一个List容器

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
package main
import "fmt"
func MapKeys[K comparable, V any](m map[K]V) []K{
r := make([]K, 0, len(m))
for k := range m {
r = append(r,k)
}
return r
}
type List[T any] struct{
head, tail *element[T]
}
type element[T any] strcut{
next *element[T]
val T
}
func (lst *List[T]) Push(v T){
if lst.head == nil{
list.head = &element[T]{val: v}
lst.tail = lst.head
}else{
lst.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next
}
}
func main()
{
var m = map[int]string{1:"2", 2:"4", 4:"8"}
fmt.Println("keys m:",MapKeys(m))
lst := List[int]{}
lst.Push(10)
fmt.Println("keys m:",lst.GetAll())
}

错误处理

通过errors.New()获取error值

也可以设定error类型实现Error()方法

协程:go

可以go一个匿名函数像是lambda一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import(
"fmt"
"time"
)
func f(from string){
for i := 0; i < 3; i++ {
fmt.Println(from, ":", i)
}
}
func main(){
f("direct")
go f("goroutine")
go func(msg string) {
fmt.Println(msg)
}("going")
time.Sleep(time.Second)
fmt.Println("done")
}

channels

连接多个协程,作用同管道

创建一个messages通道,指定string类型,通过<-发送和接收

make(chan string, 2)创建一个缓存2个值的通道

1
2
3
4
5
6
7
8
package main
import "fmt"
func main(){
message := make(chan string)
go func(){ messages <- "ping" }()
msg := <-messages
fmt.Println(msg)
}

当使用通道作为函数参数时可以指定通道是否为只读或者只写

1
2
3
4
5
6
7
func ping(pings chan<- string, msg string){
pings<- msg
}
func pong(pings <-chan string, pongs chan-< string){
msg := <-pings
pongs<- msg
}

通道选择器

通过select关键字模拟并行的协程执行(例如RPC操作)时造成的阻塞

<-time.After(time.Second)执行超时后的case

default:非阻通道操作

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"
"time"
)
func main(){
c1 := make(chan string)
c2 := make(chan string)

go func(){
time.Sleep(1*time.Second)
cd<- "one"
}()
go func(){
time.Sleep(2*time.Second)
c2<- "two"
}()
for i := 0; i < 2; i++{
select{
casr msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}

Timer and Ticker

time.NewTimer()和time_.Stop()启动和停止计时器,提供一个通道,<-timer1.C会一直阻塞直到定时器超时

ticker同timer一样,不过会重复触发直到stop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main(){
ticker := time.NewTicker(500*time.Millisecond)
done := make(chan bool)

go func()
{
for{
select{
case <-done:
return
case t := <-ticker.C:
fmt.Println("Ticker at", t)
}
}
}()
time.Sleep(1600*time.Millisecond)
ticker.stop()
done<- true
fmt.Println("Ticker stopped")
}

工作池

通过创建3个协程,jobs通道模拟任务队列,协程不断取出任务,并且当没有传递任务时是阻塞的

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
package main
import ("fmt";"time")
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs{
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results<- j*2
}
}
func main(){
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
for w := 1; w <= 3; w++{
go worker(w, jobs, results)
}
for j := 1; j <= numJobs; j++{
jobs<- j
}
close(jobs)
for a := 1; a <= numJobs; a++{
<-results
}
}

原子计数atomic

使用&ops操作地址,通过atomic进行并发安全计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import(
"fmt"
"sync"
"sync/atmoic"
)
func main(){
var ops uint64
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
for c := 0; c < 1000; c++ {
atomic.AddUint64(&ops,1)
}
wg.Done()
}()
}
wg.Wait()
fmt.Println("ops:", ops)
}

互斥锁

使用&传递互斥锁结构,不能复制;defer在函数结束时解锁

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
package main
import("fmt";"sync")
type Container struct {
mu sync.Mutex
counters map[string]int
}
func (c *Container) inc(name string){
c.mu.Lock()
defer c.mu.Unlock()
c.counters[name]++
}
func main(){
c := Container{
counters: map[string]int{"a":0, "b":0},
}
var wg sync.WaitGroup
doIncrement := func(name string, n int){
for i := 0; i < n; i++ {
c.inc(name)
}
wg.Done()
}
wg.Add(3)
go doIncrement("a",10000)
go doIncrement("b",10000)
go doIncrement("a",10000)
wg.Wait()
fmt.Println(c.counters)
}

基于协程状态互斥操作

通过通信实现共享内存,state被一个协程单独拥有就叫做控制协程,创建100个读协程和10个写协程

读协程发送读操作,控制协程接收到readop,将map[key]发回去,读协程阻塞读,然后add

写程序同理,只不过将读操作换成写操作<-write.resp

这些操作通过channel实现,并且每个op内含一个读/写通道do something

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
package main
import(
"fmt"
"math/rand"
"sync/atomic"
"time"
)
type readOp struct{
key int
resp chan int
}
type writeOp struct {
key int
val int
resp chan bool
}
func main(){
var readOps uint64
var writeOps uint64
reads := make(chan readOp)
writes := make(chan writeOp)
go func(){
var state = make(map[int]int)
for{
select{
case read := <-reads:
read.resp<- state[read.key]
case write := <-writes:
state[write.key] = write.val
write.resp<- true
}
}
}()
for r := 0; r < 100; r++{
go func() {
for{
read := readOp{
key: rand.Intn(5)
resp: make(chan int)}
reads<- read
<-read.resp
atmoic.AddUint64(&readOps,1)
time.Sleep(time.Millisecond)
}
}()
}
for w := 0; w < 10; w++{
go func(){
for{
write := writeOp{
key: and.Intn(5),
val: rand.Intn(100),
resp: make(chan bool)}
writes<- write
<-write.resp
atomic.AddUint64(&writeOps,1)
time.Sleep(time.Millisecond)
}
}()
}
time.Sleep(time.Second)
readOpsFinal := atmoic.LoadUint64(&readOps)
fmt.Println("readOps:",readOpsFinal)
writeOpsFinal := atmoic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
}

排序

原地排序,sort检查一个切片是否有序

1
2
3
4
5
6
7
strs := []string{"c","a","b"}
sort.Strings(strs)
ints := []int{7,2,4}
sort.Ints(ints)
fmt.Println("Ints:",ints)
s := sort.IntsAreSorted(ints)
fmt.Println("Sorted:", s)

Panic and Recover

panic表示运行意外错误,输出一个错误消息和协程追踪信息,并以非零状态退出程序,go中会使用返回值来标识错误

recover在panic后执行,必须在defer中使用,原因是panic触发已中止程序,而defer在程序结束执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import "fmt"
func mayPanic(){
panic("a problem")
}
func main(){
defer func(){
if r := recover(); r!=nil{
fmt.Println("Recovered. Error:\n",r)
}
}()
mayPanic()
fmt.Println("After mayPanic")
}

Defer

在程序执行后调用某个函数清理工作,跟析构函数类似

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 main
import("fmt";"os")
func main(){
f:=createFIle("/tmp/defer.txt")
defer closeFile(f)
writeFile(f)
}
func createFile(p string) *os.File{
fmt.Println("creating")
f, err := os.Create(p)
if err != nil{
panic(err)
}
return f
}
func writeFile(f *os.File){
fmt.Println("writing")
fmt.Fprintln(f, "data")
}
func closeFile(f *os.File){
fmt.Println("closing")
err := f.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}

字符串函数

go使用utf-8编码,获取的是字节数而不是字符数

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
package main

import (
"fmt"
s "strings"
)

var p = fmt.Println

func main() {

p("Contains: ", s.Contains("test", "es"))
p("Count: ", s.Count("test", "t"))
p("HasPrefix: ", s.HasPrefix("test", "te"))
p("HasSuffix: ", s.HasSuffix("test", "st"))
p("Index: ", s.Index("test", "e"))
p("Join: ", s.Join([]string{"a", "b"}, "-"))
p("Repeat: ", s.Repeat("a", 5))
p("Replace: ", s.Replace("foo", "o", "0", -1))
p("Replace: ", s.Replace("foo", "o", "0", 1))
p("Split: ", s.Split("a-b-c-d-e", "-"))
p("ToLower: ", s.ToLower("TEST"))
p("ToUpper: ", s.ToUpper("test"))
p()

p("Len: ", len("hello"))
p("Char:", "hello"[1])
}

字符串格式化

格式 输出
%v 结构体
%+v 包含字段名
%#v 产生该值的源码片段
%T 打印类型
%t bool
%d/b/x 进制
%c 字符
%f/e/E 浮点数(科学计数法表示)
%s 字符串
%q 带有双引号输出
%p 指针
-%6d -左对齐,数字表示宽度

文本模板

Must函数在Parse出错时返回panic

{{.}}被替换当前项

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
package main
import(
"os"
"text/template"
)
func main(){
t1 := template.New("t1")
t1, err := t1.Parse("Value is {{.}}\n")
if err != nil{
panic(err)
}
t1 = template.Must(t1.Parse("Value: {{.}}\n"))

t1.Execute(os.Stdout, "some text")
t1.Execute(os.Stdout, 5)
t1.Execute(os.Stdout, []string{
"Go",
"Rust",
"C++",
"C#",
})
Create := func(name, t string) *template.Template{
return template.Must(template.New(name).Parse(t))
}
t2 := Create("t2", "Name: {{.Name}}\n")
t2.Execute(os.Stdout, struct{
Name string
}{"Jane Doe"})
t2.Execute(os.Stdout, map[string]string{
"Name": "Mickey Mouse",
})
t3 := Create("t3", "{{if . -}} yes {{else -}} no {{end}}\n")
t3.Execute(os.Stdout, "no empty")
t3.Execute(os.Stdout, "")

t4 := Create("t4", "Range: {{range .}}{{.}} {{end}}\n")
t4.Execute(os.Stdout,
[]string{
"Go",
"Rust",
"C++",
"C#",
})
}

正则表达式

regexp.Compile()获得匹配规则,FindStringSubmatch包含匹配项还包含匹配表达式的那一部分,ReplaceAllString把所有匹配项替换

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
package main
import(
"bytes"
"fmt"
"regexp"
)
func main(){
match, _ := regexp.MatchString("p([a-z]+)ch", "peach")
fmt.Println(match)
r, _ := regexp.Compile("p([a-z]+)ch")

fmt.Println(r.MatchString("peach"))
fmt.Println(r.FindString("peanch punch"))
fmt.Println("idx:", r.FindStringIndex("peach punch"))
fmt.Println(r.FindStringSubmatch("peach punch"))
fmt.Println(r.FindStringSubmatchIndex("peach punch"))
fmt.Println(r,FindAllString("peach punch pinch", -1))
fmt.Println("all:", r.FindAllStringSubmatchIndex("peach punch pinch", -1))
fmt.Println(r.Match([]byte("peach")))

r = regexp.MustCompile("p([a-z]+)ch")
fmt.Println("regexp:", r)

fmt.Println(r.ReplaceAllString("a peach", "<fruit>"))

in := []byte("a peach")
out := r.ReplaceAllFunc(in, bytes.ToTupper)
fmt.Println(string(out))
}

JSON

自定义类型的字段必须以大写字母开头才是可导出,可导出字段才能JSON编码解码

1
2
3
4
5
6
7
8
9
10
11
type response struct{
Page int
Fruits []string
}

resD := &response{
1,[]string{"apple","peach","pear"}}
resB, _ := json.Marshal(resD)

str := '{"Page": 1, "Fruits": ["apple", "peach"]}'
json.Unmarshal([]byte(str), &res)

HTTP

客户端:http.get创建http.client对象并发送get请求,使用http.DefaultClient对象默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import(
"bufio"
"fmt"
"net/http"
)
func main(){
resp, err := http.Get("http://gobyexample.com")
if err != nil{
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response status:", resp.Status)

scanner := bufio.NewScanner(resp.Body)
for i := 0; scanner.Scan() && i < 5; i++{
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil{
panic(err)
}
}

服务端:handler对象实现http.Handler接口,在具有适当签名的函数上使用 http.HandlerFunc 适配器

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"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request){
fmt.Fprintf(w, "hello\n")
}

func headers(w http.ResponseWriter, req *http.Request){
for name, headers := range req.Header{
for _,j := range headers{
fmt.Fprintf(w, "%v: %v\n",name,h)
}
}
}

func main(){
http.HandleFunc("/hello", hello)
http.HandleFunc("/headers", headers)

http.ListenAndServer(":8080", nil)
}