接口
接口是一组仅包含方法名、参数、返回值的未具体实现的方法的集合
关键字interface
举例说明:有个一唱歌的方法,传入一个结构体,传的谁就会唱歌
package main
import "fmt"
// 举例说明:有个一唱歌的方法,传入一个结构体,传的谁就会唱歌
// 定义结构体type Chicken struct { Name string}
// 绑定方法func (c Chicken) Sing() { fmt.Println(c.Name, "正在唱歌...")}
// sing的方法
func sing(c Chicken) { c.Sing()}
func main() { c := Chicken{Name: "iku"} sing(c)}此时如果有其他的对象同样要调用sing方法就会报错
type Dog struct { Name string}
func (d Dog) Sing() { fmt.Println(d.Name, "正在唱歌...")}func main() { c := Chicken{Name: "iku"} sing(c) d := Dog{Name: "小黄"} sing(d)}
解决办法
// 定义接口type SingInterface interface { Sing()}
// sing的方法func sing(c SingInterface) { c.Sing()}如果要在接口里面调用打印
type SingInterface interface { Sing() GetName() string}func (d Dog) GetName() string { return d.Name}func (c Chicken) GetName() string { return c.Name}func sing(c SingInterface) { c.Sing() fmt.Println(c.GetName())}func main() { c := Chicken{Name: "iku"} sing(c) d := Dog{Name: "小黄"} sing(d)}iku 正在唱歌...iku小黄 正在唱歌...小黄接口本身不能绑定方法
接口是值类型,保存的是:值+原始类型
类型断言
在使用接口的时候,希望此时的具体类型是什么,我们可以通过断言来获取
func sing(c SingInterface) { //类型断言 c, ok := c.(Chicken) fmt.Println(c, ok) c.Sing() fmt.Println(c.GetName())}func main() { c := Chicken{Name: "iku"} sing(c) d := Dog{Name: "小黄"} sing(d)}{iku} trueiku 正在唱歌...iku{} false 正在唱歌...以上是一种类型的断言,如果是多种枚举出所有的类型
func sing(c SingInterface) { switch server := c.(type) { case Chicken: fmt.Println(server) case Dog: fmt.Println(server) default: fmt.Println("没有这个对象") }}{iku}{小黄}空接口
空接口可以接受任何类型
任何类型都实现了空接口的定义
// 空接口:一个打印的函数,可以接收任意类型的值type EmptyInterface interface{}
func Print(v EmptyInterface) { fmt.Println(v)}更简洁的写法
func Print(v interface{}) { fmt.Println(v)}func Print(v any) { fmt.Println(v)}协程和channel
协程
Goroutine是Go运行时管理的轻量级线程
以下是传统的写法,现在的模式就是接力的方式
package main
import ( "fmt" "time")
// 例子:一个耗时函数,类似于js的异步函数func shopping(name string) { fmt.Printf("开始购物,用户名:%s\n", name) time.Sleep(time.Second * 1) fmt.Printf("结束购物,用户名:%s\n", name)}
// 协程func main() { strtTime := time.Now() shopping("张三") shopping("李四") shopping("王五") fmt.Println("购买完成:", time.Since(strtTime))}开始购物,用户名:张三结束购物,用户名:张三开始购物,用户名:李四结束购物,用户名:李四开始购物,用户名:王五结束购物,用户名:王五购买完成: 3.000919s协程:类似于三个人一起购物,关键词go
func main() { strtTime := time.Now() go shopping("张三") go shopping("李四") go shopping("王五") fmt.Println("购买完成:", time.Since(strtTime))}购买完成: 0s开始购物,用户名:张三可以发现,以上的执行结果每次都不一样
简单的理解就是主线程结束的时候协程可能还没有结束,主线程结束的时候协程同步结束
一个简单的解决办法
声明一个全局变量wait,在协程函数里面–,main函数增加一个循环,wait为0退出循环
package main
import ( "fmt" "time")
// 申明一个全局变量var wait int
// 例子:一个耗时函数,类似于js的异步函数func shopping(name string) { fmt.Printf("开始购物,用户名:%s\n", name) time.Sleep(time.Second * 1) fmt.Printf("结束购物,用户名:%s\n", name) wait--}
// 协程:类似于三个人一起购物func main() { strtTime := time.Now() wait = 3 //主线程结束,协程函数跟着结束 go shopping("张三") go shopping("李四") go shopping("王五") for { if wait == 0 { break } } fmt.Println("购买完成:", time.Since(strtTime))}开始购物,用户名:张三开始购物,用户名:李四开始购物,用户名:王五结束购物,用户名:张三结束购物,用户名:李四结束购物,用户名:王五购买完成: 1.0004595s更好的方法sync.WaitGroup
package main
import ( "fmt" "sync" "time")
var wait sync.WaitGroup
// 例子:一个耗时函数,类似于js的异步函数func shopping(name string) { fmt.Printf("开始购物,用户名:%s\n", name) time.Sleep(time.Second * 1) fmt.Printf("结束购物,用户名:%s\n", name) //表示结束 wait.Done()}
// 协程:类似于三个人一起购物func main() { strtTime := time.Now() wait.Add(3) //主线程结束,协程函数跟着结束 go shopping("张三") go shopping("李四") go shopping("王五") //等待协程函数 wait.Wait() fmt.Println("购买完成:", time.Since(strtTime))}另外一种情况
如果wait是在main内部声明,shopping需要传参wait
func shopping(name string, wait sync.WaitGroup) { fmt.Printf("开始购物,用户名:%s\n", name) time.Sleep(time.Second * 1) fmt.Printf("结束购物,用户名:%s\n", name) //表示结束 wait.Done()}func main() { var wait sync.WaitGroup strtTime := time.Now() wait.Add(3) //主线程结束,协程函数跟着结束 go shopping("张三", wait) go shopping("李四", wait) go shopping("王五", wait) //等待协程函数 wait.Wait() fmt.Println("购买完成:", time.Since(strtTime))}但是此时wait.Done()和wait.Add(3)不是同一个wait,需要接指针
func shopping(name string, wait *sync.WaitGroup) { fmt.Printf("开始购物,用户名:%s\n", name) time.Sleep(time.Second * 1) fmt.Printf("结束购物,用户名:%s\n", name) //表示结束 wait.Done()}func main() { var wait sync.WaitGroup strtTime := time.Now() wait.Add(3) //主线程结束,协程函数跟着结束 go shopping("张三", &wait) go shopping("李四", &wait) go shopping("王五", &wait) //等待协程函数 wait.Wait() fmt.Println("购买完成:", time.Since(strtTime))}channel
协程里面产生了数据,怎么传递给主线程呢?
或者是怎么传递给其他协程函数呢?
这个时候 channel来了
例子:
还是和上面一样,希望在主线程里面获取到每个人消费了多少金额
定义信道
var 信道名 chan 存储的类型
// 定义信道var moneyChan chan int以上是nil值
var moneyChan = make(chan int) //声明并初始化一个长度为0的信道信道长度可以按以下理解:
- 信道就像一个有固定容量的仓库
- 发送方像供应商,往仓库里存放货物
- 接收方像客户,从仓库里取走货物
- 容量限制是仓库最多能同时存放多少货物
关键区别
- 对于无缓冲信道(容量为0):发送和接收必须同步,没有中间存储
- 对于有缓冲信道:发送方可以在不阻塞的情况下发送多个值,只要不超过容量限制
信道赋值语句
moneyChan <- 100func pay(name string, money int, wait *sync.WaitGroup) { fmt.Printf("开始购物,用户名:%s\n", name) time.Sleep(time.Second * 1) fmt.Printf("结束购物,用户名:%s\n", name) //信道赋值语句 moneyChan <- money //表示结束 wait.Done()}func main() { var wait sync.WaitGroup startTime := time.Now() wait.Add(3) //主线程结束,协程函数跟着结束 go pay("张三", 10, &wait) go pay("李四", 20, &wait) go pay("王五", 30, &wait) //等待协程函数 wait.Wait() //希望在主线程里面获取到每个人消费了多少 fmt.Println("购买完成:", time.Since(startTime))}如果此时直接运行会报错
fatal error: all goroutines are asleep - deadlock!主线程接收
for { money, ok := <-moneyChan fmt.Println(money) if !ok { break } }开始购物,用户名:王五开始购物,用户名:张三开始购物,用户名:李四结束购物,用户名:王五30 true结束购物,用户名:张三10 true结束购物,用户名:李四20 true此时还是会发生报错
fatal error: all goroutines are asleep - deadlock!但是报错数量只有一个
解决办法:在合适的时机关闭信道
再定义一个协程函数,在协程函数里面等待结束后关闭信道
go func() { wait.Wait() close(moneyChan) }()开始购物,用户名:李四开始购物,用户名:王五开始购物,用户名:张三结束购物,用户名:张三结束购物,用户名:王五结束购物,用户名:李四10 true30 true20 true0 false购买完成: 1.0004089s另一种写法
go func() { defer close(moneyChan) wait.Wait() //close(moneyChan) }() for money := range moneyChan { fmt.Println(money) }select
如果一个协程函数,往多个channel里面写东西,在主线程里面怎么拿数据呢?
go为我们提供了select,用于异步的从多个channel里面去取数据
继续上面的例子,在协程函数里面发送两个信道
以下是一种按照直觉的写法
var moneyChan1 = make(chan int) //声明并初始化一个长度为0的信道var nameChan1 = make(chan string) //声明并初始化一个长度为0的信道
func send(name string, money int, wait *sync.WaitGroup) { fmt.Printf("开始购物,用户名:%s\n", name) time.Sleep(time.Second * 1) fmt.Printf("结束购物,用户名:%s\n", name) //信道赋值语句 moneyChan1 <- money nameChan1 <- name //表示结束 wait.Done()}func main() { var wait sync.WaitGroup startTime := time.Now() wait.Add(3) //主线程结束,协程函数跟着结束 go send("张三", 10, &wait) go send("李四", 20, &wait) go send("王五", 30, &wait)
//再定义一个协程函数 go func() { defer close(moneyChan1) defer close(nameChan1) wait.Wait() }()
var moneyList []int var nameList []string go func() { for money := range moneyChan1 { //fmt.Println(money) moneyList = append(moneyList, money) } }()
for name := range nameChan1 { //fmt.Println(money) nameList = append(nameList, name) }
//等待协程函数 //wait.Wait() //希望在主线程里面获取到每个人消费了多少 fmt.Println("购买完成:", time.Since(startTime)) fmt.Println("moneyList:", moneyList) fmt.Println("moneyList:", nameList)}开始购物,用户名:李四开始购物,用户名:张三开始购物,用户名:王五结束购物,用户名:王五结束购物,用户名:李四结束购物,用户名:张三购买完成: 1.0007567smoneyList: [30 20 10]moneyList: [王五 李四 张三]以上的代码能够正常运行
可以使用select关键词
for { select { case money := <-moneyChan1: moneyList = append(moneyList, money) case name := <-nameChan1: nameList = append(nameList, name) } }现在并不能退出循环,需要声明一个用于关闭的信道
var doneChan = make(chan struct{}) go func() { defer close(moneyChan1) defer close(nameChan1) defer close(doneChan) wait.Wait() //close(moneyChan) }() for { select { case money := <-moneyChan1: moneyList = append(moneyList, money) case name := <-nameChan1: nameList = append(nameList, name) case <-doneChan: fmt.Println("结束") fmt.Println("购买完成:", time.Since(startTime)) fmt.Println("moneyList:", moneyList) fmt.Println("nameList:", nameList) return } }现在不能在主线程拿到moneyList和nameList,优化的方式,创建一个监听函数
var event = func() { for { select { case money := <-moneyChan1: moneyList = append(moneyList, money) case name := <-nameChan1: nameList = append(nameList, name) case <-doneChan: return } } } event() fmt.Println("购买完成:", time.Since(startTime)) fmt.Println("moneyList:", moneyList) fmt.Println("nameList:", nameList)协程超时处理
var done = make(chan struct{})
func event() { fmt.Println("event执行开始") time.Sleep(2 * time.Second) fmt.Println("event执行结束")}func main() { go event() select { case <-done: fmt.Println("协程执行完毕") case <-time.After(1 * time.Second): fmt.Println("协程执行超时") }}


