闭包
闭包为一种特殊的函数
其特殊的性质在于,利用闭包可以在函数外访问到函数内的局部变量。
package main
import (
"fmt"
)
func a() func() int {
i := 0
b := func() int {
i++
fmt.Println(i)
return i
}
return b
}
func main() {
c := a()
c()//1
c()//2
c()//3
a() //不会输出i
}
在c:=a()语句结束的时候,系统为a中的匿名函数分配了地址,而在之后的调用中,他们操作同一个地址,如果将代码稍作修改,如下
func main() {
c1 := a()
c2 := a()
c1()//1
c1()//2
c1()//3
c2()//1
a() //不会输出i
}
此时c1和c2操作的并不是一块地址
详见如下
(18条消息) 前端面试题:闭包_ღ故里᭄ꦿ࿐的博客-CSDN博客_前端面试题闭包
并发
go的并发特性’
通过通道来共享内存 而不是通过共享内存来传递信息
func f(){
fmt.print("hello")
}
func main(){
go f()
fmt.print("world")
}
go f()即重新申请一个goroutine
打印的信息应该为
world
因为启动一个goroutine需要时间,而当main函数结束的时候,整个程序结束,往往无法打印出"hello",而如果想要打印出"hello",则可以在程序结尾加一个time.sleep(time.second)来使主程序休眠一秒以等待hello的打印
而如果每一次都要在程序结尾加一个time.sleep()太过生硬,也会浪费太多时间,1s对一个程序来说太久了,而不管你的时间把控的多么好,你总要面临go并发还没有结束,主程序就已经结束的风险,或者牺牲时间来保证程序的正常运行,还好,go的设计并不是这么的不人性化
var wg sync.WaitGroup
func hello() {
defer wg.Done()
fmt.Println("Hello Goroutine!")
}
func main() {
wg.Add(1)
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
wg.Wait()
}
这样,当程序检测到并发任务已经全部完成,就会自动结束,确实很人性化。需要注意sync.WaitGroup是一个结构体,传递的时候要传递指针。
而如果只是进行这样简单的在打印hello world,做到这里确实足够了,你可以快速的打印出一个完整的hello world,而在实际开发中,我们往往利用并发来进行计算或者后端数据的处理,当处理结束之后,将处理结果或计算结果传回同意再处理,这才是go高并发的魅力!想想看,当一件庞大的事情交给1000个人来算,在将他们算出的结果进行处理,我们可以省去多少时间,但是问题在于,我们如何传递信息,与人而言,人可以通过语言来传递他们计算的结果,而goroutine也有他们的语言,就是
channel
var 变量 chan 元素类型
通道需要声明之后再使用
make(chan 元素类型,[缓冲大小])
缓冲大小是可选的,而是否选择缓冲大小则决定了该通道的,类型,这个类型在后面有大用,但是我们先来了解一下channel 的基本用法
通道有发送(send)、接收(receive)和关闭(close)三种操作。
ch := make(chan int)
ch<-10//接收
var num=<-ch//发送
close(ch)//关闭
就是这么简单,我们来运行下面一个程序
for {
ch := make(chan int)
go func() {
ch <- 10
fmt.Println("send")
}()
a := <-ch
fmt.Println("receive", a)
time.Sleep(time.Second)
}
观察运行结果,很奇怪,明明只有main里面是有sleep 的,为什么另外一个他们的打印总是成对出现的,不应该是大量的”send“夹杂着一些"receive10"吗?
原因是因为管道的阻塞
管道的类型成为无缓冲的管道和有缓冲的管道(还有单向通道)
上文中我们使用的是无缓冲的管道,当我发送信息的时候,我必须等待管道内的信息先被接收,保证管道的整洁,同理,我接受信息的时候,管道内必须要有信息,否则我必须要等待
而另一种有缓冲的管道,则相当于一个柜子,我们可以把信息缓存在里面,等别人待会来取
make(chan 变量类型,[缓冲大小]) 不如用一个有缓冲的管道改良上面的代码试试把,单向通道大家可以自己去了解,如果有需要可以联系我补充
锁
var x int64
var wg sync.WaitGroup
func add() {
for i := 0; i < 5000; i++ {
x = x + 1
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
并发还有一大特性,就是锁,因为连个协程是相互独立的,所以当他们对一块地址进行操作的时候,就会发生争抢,运行上面代码,我们想得到的应该是10000,但是运行结果确实随机的,因为发生了内存的争抢,而为了避免这样的错误,来引入一个新的概念,改良代码如下
var x int64
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
for i := 0; i < 5000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
这种锁是互斥锁,当一个协程lock了以后,别的协程就无法访问,直到开锁
而除了互斥锁还存在一种读写锁,即当你为内存加上读锁之后,其它协程依然可以进行写的操作,而当你进行写锁,那无论都还是写都必须等待