限制协程执行数量的基本方法: 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 package mainimport ( "fmt" "sync" "time" ) func job (index int ) { time.Sleep(time.Millisecond * 500 ) fmt.Printf("执行完毕,序号:%d\n" , index) } var pool chan struct {}func main () { maxNum := 10 pool = make (chan struct {}, maxNum) wg := sync.WaitGroup{} for i := 0 ; i < 100 ; i++ { pool <- struct {}{} wg.Add(1 ) go func (index int ) { defer wg.Done() defer func () { <-pool }() job(index) }(i) } wg.Wait() }
函数执行超时的控制代码怎么写 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" "time" ) func job () chan string { ret := make (chan string ) go func () { time.Sleep(time.Second * 5 ) ret <- "success" }() return ret } func run () (interface {}, error ) { c := job() select { case r := <-c: return r, nil case <-time.After(time.Second * 3 ): return nil , fmt.Errorf("time out" ) } } func main () { fmt.Println(run()) }
nil不等于nil的问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { var f func () var a *struct {} list := []interface {}{f, a} for _, item := range list { if reflect.ValueOf(item).IsNil() { fmt.Println("nil" ) } } }
Interface 有两部分组成:类型和值
defer定义函数时的参数问题 1 2 3 4 a := 1 defer fmt.Println(a) a++
defer里链式调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "fmt" type test struct {}func NewTest () *test { return &test{} } func (this *test) do(i int ) *test { fmt.Println(i) return this } func main () { t := NewTest() defer t.do(1 ).do(2 ).do(5 ) t.do(3 ) }
循环执行defer
1 2 3 4 5 6 7 8 9 func main () { for i := 0 ; i < 3 ; i++ { defer func () { fmt.Println(i) }() } }
defer和panic哪个先执行
1 2 3 4 5 6 7 8 9 func main () { defer func () {fmt.Println("打印前" )}() defer func () {fmt.Println("打印中" )}() defer func () {fmt.Println("打印后" )}() panic ("触发异常" ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { func () { defer func () { fmt.Println("打印前" ) }() defer func () { fmt.Println("打印中" ) }() defer func () { fmt.Println("打印后" ) }() panic ("触发异常1" ) }() panic ("触发异常2" ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { defer func () { defer func () { fmt.Println("打印前" ) }() defer func () { fmt.Println("打印中" ) }() defer func () { fmt.Println("打印后" ) }() panic ("触发异常1" ) }() panic ("触发异常2" ) }
n++不靠谱 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 func main () { for i := 0 ; i < 1000000 ; i++ { go func () { n++ }() } fmt.Println(n) } func main () { n := 0 wg := sync.WaitGroup{} for i := 0 ; i < 1000000 ; i++ { wg.Add(1 ) go func () { defer wg.Done() n++ }() } wg.Wait() fmt.Println(n) }
n++这个过程并不是原子的,有三个步骤:
这个过程中,其他协程也会进来读取n的值,因此不是原子的
所谓的原子,执行过程中不会发生上下文(线程的切换)的切换
两种办法解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { n := 0 locker := sync.Mutex{} wg := sync.WaitGroup{} for i := 0 ; i < 1000000 ; i++ { wg.Add(1 ) go func () { defer wg.Done() defer locker.Unlock() locker.Lock() n++ }() } wg.Wait() fmt.Println(n) }
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { var n int32 = 0 wg := sync.WaitGroup{} for i := 0 ; i < 1000000 ; i++ { wg.Add(1 ) go func () { defer wg.Done() atomic.AddInt32(&n, 1 ) }() } wg.Wait() fmt.Println(n) }
go常见的并发模式 go的并发模式有哪些,几个点:
go有协程以及CSP调度模型,是进行并发运行的基础
可以使用协程来完成 ”并发编程“
go并发编程哲学:不要通过共享内存来通信,而应通过通信来共享内存
并发编程的重点在于:如何精准的控制 “共享数据”
基本:通过协程来执行并发任务,通过channel来通信
1 2 3 4 5 6 7 8 9 10 11 12 func main () { wg := sync.WaitGroup{} for i := 0 ; i < 3 ; i++ { wg.Add(1 ) go func (input int ) { defer wg.Done() fmt.Println(input * 2 ) }(i) } wg.Wait() }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { c := make (chan int , 3 ) for i := 0 ; i < 3 ; i++ { go func (input int ) { c <- input * 2 }(i) } for i := 0 ; i < cap (c); i++ { fmt.Println(<-c) } }
生产者模式
第一种写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func Producer (out chan int ) { defer close (out) for i := 0 ; i < 5 ; i++ { out <- i * 2 time.Sleep(time.Second * 1 ) } } func Consumer (out chan int ) { for item := range out { fmt.Println(item) } } func main () { c := make (chan int ) go Producer(c) Consumer(c) }
第二种写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func Producer (out chan int ) { defer close (out) for i := 0 ; i < 5 ; i++ { out <- i * 2 time.Sleep(time.Second * 1 ) } } func Consumer (out chan int , r chan struct {}) { for item := range out { fmt.Println(item) } r <- struct {}{} } func main () { c := make (chan int ) r := make (chan struct {}) go Producer(c) go Consumer(c, r) <-r }
推荐写法:
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 func Producer (out chan int ) { defer close (out) for i := 0 ; i < 5 ; i++ { out <- i * 2 time.Sleep(time.Second * 1 ) } } func Consumer (out chan int ) (r chan struct {}) { r = make (chan struct {}) go func () { defer func () { r <- struct {}{} }() }() for item := range out { fmt.Println(item) } return r } func main () { c := make (chan int ) go Producer(c) r := Consumer(c) <-r }
优胜劣汰模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" "math/rand" "time" ) func job2 () int { rand.Seed(time.Now().Unix()) result := rand.Intn(5 ) time.Sleep(time.Second * time.Duration(result)) return result } func main () { c := make (chan int ) for i := 0 ; i < 5 ; i++ { go func () { c <- job2() }() } fmt.Println("最快用了:" , <-c, "秒" ) }
协程为什么总是先输出倒数第一个 1 2 3 4 5 6 7 8 9 10 11 12 func main () { runtime.GOMAXPROCS(1 ) wg := sync.WaitGroup{} wg.Add(5 ) for i := 0 ; i < 5 ; i++ { go func (i int ) { defer wg.Done() fmt.Println(i) }(i) } wg.Wait() }
首先 runtime.GOMAXPROCS,golang默认使用所以的cpu核,runtime.GOMAXPROCS(1) 就变成单核运行了
单核情况下,所有goroutine运行在同一个线程(M)中,线程维护一个上下文(P)
在上面程序中,我们循环创建了若干线程,且是单核
在P就绪后,开始执行。默认先执行的是最后一个创建的协程
然后再继续执行其他协程,此次是按照顺序来的
要想按照顺序输出,怎么解决: 再加一个协程就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { runtime.GOMAXPROCS(1 ) wg := sync.WaitGroup{} wg.Add(6 ) for i := 0 ; i < 5 ; i++ { go func (i int ) { defer wg.Done() fmt.Println(i) }(i) } go func () { defer wg.Done() fmt.Println("开始解决" ) }() wg.Wait() }
写一个带过期机制的kv 获取map 原始的map不是线程安全的,要使用sync.map,使用time.After 实现简单的过期机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var kv sync.Mapfunc Set (key string , value interface {}, expire time.Duration) { kv.Store(key, value) time.AfterFunc(expire, func () { kv.Delete(key) }) } func main () { Set("id" , 101 , time.Second*5 ) Set("name" , "zhangsan" , time.Second*8 ) for { fmt.Println(kv.Load("id" )) fmt.Println(kv.Load("name" )) time.Sleep(time.Second) } }
谈一谈Go的链表操作 谈一下双向链表和单向链表的区别
go自带一个双向链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "container/list" "fmt" ) func main () { data := list.New() e8 := data.PushBack(8 ) data.PushBack(9 ) data.PushBack(10 ) data.PushFront(7 ) data.InsertAfter(8.5 , e8) for e := data.Front(); e != nil ; e = e.Next() { fmt.Printf("%v " , e.Value) } }
如何使用golang定义枚举 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" type UserType int func (u UserType) String() string { switch u { case 0 : return "学生" case 1 : return "老师" default : return "领导" } } const ( Student UserType = iota Teacher Leader ) func main () { fmt.Println(Student, Teacher, Leader) }
go的struct能不能比较 struct中包含了 map slice func 是不能比较的
结论:
相同结构,只要成员类型都可以比较,就能比较
不相同的结构,如果能互相转化,也能比较。前提是成员都是可以比较的
用go实现一个简单的set 目前go的标准库里没有set
所谓的set 简单来说就是一个集合,里面的内容不能重复
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 package mainimport ( "bytes" "fmt" ) type Empty struct {}type Set map [interface {}]Emptyfunc (this Set) Add(vs ...interface {}) Set { for _, v := range vs { this[v] = Empty{} } return this } func (this Set) String() string { var buf bytes.Buffer for k, _ := range this { if buf.Len() > 0 { buf.WriteString("," ) } buf.WriteString(fmt.Sprintf("%v" , k)) } return buf.String() } func NewSet () Set { return make (map [interface {}]Empty) } func main () { set := NewSet().Add(1 , 2 , 3 , 4 , 2 , 1 , 3 ) fmt.Println(set) }
go的切片浅拷贝和深拷贝的写法和区别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { a := []int {1 , 2 , 3 } b := a fmt.Println(a, b) } func main () { a := []int {1 , 2 , 3 } b := make ([]int , len (a), cap (a)) copy (b, a) a[1 ] = 100 b[1 ] = 90 fmt.Println(a, b) }
说一说go的内存逃逸分析 逃逸分析:go在编译阶段确定内存是分配在栈上还是堆上的一种行为
栈内存分配和释放非常快
堆内存需要依靠Go垃圾回收(GC 占用CPU)
通过逃逸分析,可以尽量把那些不需要分配到堆上的变量直接分配到栈上
Go的主要目的并不希望程序员关注分配,而是通过编译时的代码分析自动决定
go build -gcflags=-m main.go
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 func test () []int { a := []int {1 , 2 , 3 } a[1 ] = 4 return a } package mainimport "fmt" type User struct { id int } func NewUser () User { return User{101 } } func main () { u := NewUser() fmt.Println(u) }
说一说Go的单例模式 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 mainimport ( "fmt" "sync" ) type WebConfig struct { Port int } var cc *WebConfigvar mu sync.Mutexfunc GetConfig () *WebConfig { mu.Lock() defer mu.Unlock() if cc == nil { cc = &WebConfig{Port: 8080 } } return cc } func main () { c := GetConfig() c2 := GetConfig() c3 := GetConfig() fmt.Println(c, c2, c3) fmt.Println(c == c2, c2 == c3) }
事实上go提供了内置方法
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 WebConfig struct { Port int } var cc *WebConfigvar once sync.Oncefunc GetConfig () *WebConfig { once.Do(func () { cc = &WebConfig{Port: 8080 } }) return cc } func main () { c := GetConfig() c2 := GetConfig() c3 := GetConfig() fmt.Println(c, c2, c3) fmt.Println(c == c2, c2 == c3) }
说一说Go的简单工厂模式 可以将接口与具体实现分离,根据需要(根据参数)实例化
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 package mainimport "fmt" type User interface { GetRole() string } type Member struct {}func (m *Member) GetRole() string { return "会员用户" } type Admin struct {}func (a *Admin) GetRole() string { return "后台管理用户" } const ( Mem = iota Adm ) func CreateUser (t int ) User { switch t { case Mem: return new (Member) case Adm: return new (Admin) default : return new (Member) } } func main () { fmt.Println(CreateUser(Adm).GetRole()) }
Go的抽象工厂方法 接着上面的简单工厂模式
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 package mainimport "fmt" type User interface { GetRole() string } type Member struct {}func (m *Member) GetRole() string { return "会员用户" } type Admin struct {}func (a *Admin) GetRole() string { return "后台管理用户" } const ( Mem = iota Adm ) func CreateUser (t int ) User { switch t { case Mem: return new (Member) case Adm: return new (Admin) default : return new (Member) } } type AbstractFactory interface { CreateUser() User } type MemberFactory struct {}func (m *MemberFactory) CreateUser() User { return &Member{} } type AdminFactory struct {}func (a *AdminFactory) CreateUser() User { return &Admin{} } func main () { var fact AbstractFactory = new (AdminFactory) fmt.Println(fact.CreateUser().GetRole()) }
不动原来的代码,一旦增加了什么类别,只要增加如下代码:
1 2 3 4 5 6 7 type MemberFactory struct {}func (m *MemberFactory) CreateUser() User { return &Member{} }
请写一个Go的装饰器模式的例子 允许向一个现有对象添加新的功能,同时又不改变其结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "net/http" func index (writer http.ResponseWriter, request *http.Request) { writer.Write([]byte ("index" )) } func main () { http.HandleFunc("/" , index) http.ListenAndServe(":8080" , nil ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "net/http" func Decorator (f http.HandlerFunc) http.HandlerFunc { return func (writer http.ResponseWriter, request *http.Request) { writer.Write([]byte ("this is " )) f(writer, request) } } func index (writer http.ResponseWriter, request *http.Request) { writer.Write([]byte ("index" )) } func main () { http.HandleFunc("/" , Decorator(index)) http.ListenAndServe(":8080" , nil ) }
请简述go channel的底层机制 源码在 runtime/chan.go
重点关注4个部分:
buf指向一个循环队列
sendx和recvx用于记录buf发送和接收的index
lock 互斥锁
sendq和recvq 等待(阻塞)列表
其他的:
qcount:队列剩余数
dataqsize:缓冲区大小
1 2 3 4 5 6 7 8 9 10 ch := make (chan int , 3 ) ch <-1 (send) sendx = 1 recvx = 0
一旦缓存满了……
回答的基本点:
chan创建在堆中,返回指针
使用环形队列作为缓冲区
每次操作都要加锁,并更新操作(send或recv的index)
缓冲满了,进入等待队列,并让出M。等待被唤醒
被唤醒后,重新加入G队列
读写关闭的channel是啥后果 简述Go协程调度机制 https://medium.com/@ankur_anand/illustrated-tales-of-go-runtime-scheduler-74809ef6d19b
https://lessisbetter.site/2019/03/10/golang-scheduler-1-history/
https://zhuanlan.zhihu.com/p/27056944
https://www.kancloud.cn/aceld/golang/1958305
回到要点:
G、P、M是啥,基本关系
如何找G
阻塞过程
自旋
Go的协程调度就是 P将G合理的分配给某个M的过程
简述Raft协议:选举机制 分布式集群:
一旦单节点宕机,其他节点依然能提供服务
关键的是:数据并不会丢失。其中一个重要过程就是数据一致性
要实现一致性:一般来讲 节点中需要有个主节点来对数据日志进行统一管理(复制)
正常情况下,A会赢得选举(大多数选票)
于是,它会立刻给所有节点发送心跳消息,避免其余节点触发新的选举
不正常情况,由于网络等原因,A和B也许会同时发起选举,
注意:在同一个任期内,C只能给A或B投一票。先来先得
假设A先到,此时A胜出。还是那样,A胜出后会发出心跳消息。B发现A的term比他大,则会自觉的改成follower,并更新term
简述Raft协议:数据复制过程(初级) 让这个数据同步、可恢复,将这命令持久化到日志里面
基本过程:所有日志都必须首先提交至leader节点
leader加入本地日志
leader要求followers同步日志
leader等待多数节点的反馈(不是全部)
上一步成功后leader确认操作ok,并修改本地状态和存储
发出心跳要求 follower也提交并存储
最终一致性
框架中路由实现:手撸前缀树 特点:
根节点不包含字符
每个节点的所有子节点包含的字符都不相同
一个节点的所有子节点都有相同的前缀
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 package mainimport "fmt" type Trie struct { root *Node } func NewTrie () *Trie { return &Trie{root: NewNode()} } type Node struct { isEnd bool children map [string ]*Node } func NewNode () *Node { return &Node{children: make (map [string ]*Node)} } func (this *Trie) Insert(str string ) { current := this.root for _, item := range []rune (str) { if _, ok := current.children[string (item)]; !ok { current.children[string (item)] = NewNode() } current = current.children[string (item)] } current.isEnd = true } func (this *Trie) Search(str string ) bool { current := this.root for _, item := range []rune (str) { if _, ok := current.children[string (item)]; !ok { return false } current = current.children[string (item)] } return current.isEnd } func test () { strs := []string {"go" , "gin" , "golang" , "goapp" , "guest" } tree := NewTrie() for _, s := range strs { tree.Insert(s) } strs = append (strs, "gi" , "gogo" , "gia" ) for _, s := range strs { fmt.Println(tree.Search(s)) } } func main () { test() }
读取文件:如何统计文本的行数 这里用到的是bufio.scanner
好比是一个带缓冲区的迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "bufio" "fmt" "log" "os" ) func main () { file, err := os.Open("pkg/mylog" ) if err != nil { log.Fatalln(err) } scanner := bufio.NewScanner(file) count := 0 for scanner.Scan() { count++ } fmt.Println("一共有" , count, "行" ) }
Sync.Pool的基本用法 sync.pool :协程安全、可伸缩的、用于存放可重用对象的容器
原始目的是:
存放已分配但暂时用不到的对象,需要时直接从pool中取,然后放回 以减少GC回收的压力
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 mainimport ( "fmt" "log" "sync" ) var p *sync.Pooltype User struct { Name string } func main () { p = &sync.Pool{ New: func () interface {}{ log.Println("create user" ) return &User{Name: "zhangsan" } }, } u1 := p.Get().(*User) fmt.Println(u1) u1.Name = "lisi" p.Put(u1) u2 := p.Get() fmt.Println(u2) }