Golang 学习笔记07-接口和协程相关

Golang 学习笔记07-接口和协程相关
SEAlencehe接口
接口是一组仅包含方法名、参数、返回值的未具体实现的方法的集合
关键字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} true
iku 正在唱歌...
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 <- 100
func 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 true
30 true
20 true
0 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.0007567s
moneyList: [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("协程执行超时")
}
}


