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 mainimport ( "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 mainimport "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 mainimport ( "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 mainimport "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 mainimport "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 mainimport ( "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 mainimport "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 mainimport ( "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 mainimport ("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 mainimport ( "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 mainimport ("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 mainimport ( "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 mainimport "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 mainimport ("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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 ) }