好久没有跟新过文章了,小编最近有点忙,写文章的频率下降了许多,但是还是会持续跟新的,希望关注的同学仔细学习。
首先讲一下接口具体是个啥?小白可以结合官方定义和小编自己的理解共同学习下
官方解释:接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。
自己理解:面向过程的开发 变成 面向对象编程;相当于生成了一个类,需要这个类的功能直接调用就好,不用再重新定义
怎么做?简单来说就是:接口调用(结构体作为函数入参):第二步和第三步可以合并
第一步:结构体
type User struct {
属性名 值
..................................
}
第二步:定义方法
func (z *User ) Pay(amount int64) {
fmt.Printf("测试保留两位小数:%.2f。\n", float64(amount/100))
}
第三步:定义方法
func Checkout(obj *User ) {
obj.Pay(100)
}
第四步:主方法调用
func main() {
Checkout(&User {})
}
输出值:测试保留两位小数:100.00
最基础的就是这样的,其余的以后会结合项目讲解,先打好基础,后面学习才会更轻松
管道(channel)又是个啥?等小编一一解释(这里有必要学习一下调度机制GPM,这个后面小编会单独讲解)
官方解释:可以看作是一个通信的桥梁,用于连接不同操作的 goroutine。当多个 goroutine 同时访问同一资源时,我们就需要使用锁或管道来协调它们的访问,锁具有一定的局限性,因为当我们使用锁时,它只允许一个 goroutine 访问资源,而通常情况下我们需要更高效的方法协调 goroutine 的并发操作,这时就需要用到管道。并且管道是一种并发安全的数据结构
自己理解:一个goroutine的队列,保证goroutine的有序执行
了解知识:并发模型
线程&锁模型
Actor模型
CSP模型
Fork&Join模型
下面以代码的形式来理解下:
代码片段1:
func hello() {
fmt.Println("hello")
}
func main() {
hello()
fmt.Println("你好")
}
输出:
hello
你好
代码片段2:
func hello() {
fmt.Println("hello")
}
func main() {
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("你好")
}
输出:
你好
为什么会出现这样的情况?
go关键字:启动一个 goroutine 去执行 hello 这个函数,这样可以模拟并发
为什么只执行了最后的输出?
其实在 Go 程序启动时,Go 程序就会为 main 函数创建一个默认的 goroutine
在上面的代码中我们在 main 函数中使用 go 关键字创建了另外一个 goroutine 去执行 hello 函数,而此时 main goroutine 还在继续往下执行,我们的程序中此时存在两个并发执行的 goroutine。
当 main 函数结束时整个程序也就结束了,同时 main goroutine 也结束了,所有由 main goroutine 创建的 goroutine 也会一同退出。也就是说我们的 main 函数退出太快,另外一个 goroutine 中的函数还未执行完程序就退出了,导致未打印出“hello”。
图形解释:(解释代码片段1)
所以要有输出两个值的效果就需要sync 包中的WaitGroup的功能
var wg sync.WaitGroup
func hello() {
fmt.Println("hello")
wg.Done() // 当前goroutine完成
}
func main() {
wg.Add(1) // 登记1个goroutine
go hello()
fmt.Println("你好")
wg.Wait() // 阻塞等待的goroutine完成
}
输出:
hello
你好
图形解释:
虽然可以使用共享内存进行数据交换,但是共享内存在不同的 goroutine 中容易发生竞态问题,为了保证数据交换的正确性,很多并发模型中必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。
Go语言采用的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。
重点:如果说 goroutine 是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。
初始化管道:
var ch1 chan int // 声明一个传递整型的通道
var ch2 chan bool // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道
ch4 := make(chan int)
ch5 := make(chan bool, 1) // 声明一个缓冲区大小为1的通道
管道的使用:将一个值发送到通道中
ch1 <- 10
管道的使用:从一个通道中接收值
x := <- ch1
管道的使用:关闭管道
close(ch1 )
基础使用方法已经讲解完成,后面延伸讲一下:
无缓冲管道:
func main() {
ch := make(chan int)
ch <- 10
fmt.Println("发送成功")
}
报错信息:
fatal error: all goroutines are asleep - deadlock! //所有goroutine都处于休眠状态-死锁!
goroutine 1 [chan send]:
首先无缓冲通道ch上的发送操作会阻塞,直到另一个 goroutine 在该通道上执行接收操作,这时数字10才能发送成功,两个 goroutine 将继续执行。
相反,如果接收操作先执行,接收方所在的 goroutine 将阻塞,直到 main goroutine 中向该通道发送数字10。
使用无缓冲通道进行通信将导致发送和接收的 goroutine 同步化。因此,无缓冲通道也被称为同步通道
解决办法1:为什么这个就可以?
首先无缓冲通道ch上的发送操作会阻塞,直到另一个 goroutine 在该通道上执行接收操作,这时数字10才能发送成功,两个 goroutine 将继续执行。
相反,如果接收操作先执行,接收方所在的 goroutine 将阻塞,直到 main goroutine 中向该通道发送数字10。
使用无缓冲通道进行通信将导致发送和接收的 goroutine 同步化。因此,无缓冲通道也被称为同步通道
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)
go recv(ch)
ch <- 10
fmt.Println("发送成功")
}
解决办法2:缓冲管道
func main() {
ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
ch <- 10
fmt.Println("发送成功")
}
现阶段理解到这个程度就可以了,后续深入的小编再讲,结合项目实战讲