这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
nil
nil,在Go中较为常见的一个标识符(注意不是关键字,可用定义未nil的变量),常用来判断是否出现错误
在Go中,通常的错误处理如下:
ret, err := CustomFunc()
if err != nil {
panic(err)
}
那么为什么是err ≠ nil时表示未出现错误(最开始我还以为nil表示null,是出现错误呢....)
在Go中,如果一个指针未赋值(unset),那么初始值是nil
func main() {
var p *int
fmt.Println(p)
}
输出:<nil>
当没有出现错误时,不向返回的err赋值,或者说直接赋值一个nil返回来表示未出现错误
同时,nil并不能直接进行比较
fmt.Println(nil == nil)
//output: invalid operation: nil == nil (operator == not defined on untyped nil)
更加详细的可用参考下面两个博客:
Golang中的nil,没有人比我更懂nil! - 知乎 (zhihu.com)
并发编程
在Go语言中,每一个并发的执行单元叫作一个goroutine。
通常的使用:使用go关键字,然后后面跟需要执行的函数体即可如下:
func main() {
var wait sync.WaitGroup
wait.Add(1)
go loopPrint(10, &wait)
fmt.Println("i am main")
wait.Wait()
}
func loopPrint(n int, wait *sync.WaitGroup) {
for i := 0; i < n; i++ {
fmt.Println(i)
}
fmt.Println("over")
wait.Done()
}
/*输出
i am main
0
1
2
3
4
5
6
7
8
9
over
*/
这里面多了一点东西sync.WaitGroup,这个主要是为了让主线程等待goroutine结束,不然如果主线程提前结束了,而goroutine还没结束的话,goroutine将提前结束(主线程没了,goroutine也就没了)
如何获取goroutine执行后的返回值或者说执行后的结果呢
使用Channel可用与goroutine之间的通信,通过make创建存放指定数据的channel
channel = make(chan string, 2) => 创建缓存为2的channel
channel使用:
- 将值存入channel:
channel <- "ok" - 取出一个值赋给指定变量:
a := <-channel - 取出值但不使用:
<-channel
使用channel接收goroutine的处理后的结果:
var syncChan = make(chan string)
func main() {
var wait sync.WaitGroup
wait.Add(1)
go loopPrint(10, &wait)
fmt.Println("i am main")
fmt.Println(<-syncChan)
fmt.Println("get the channel value")
close(syncChan) // close channel
wait.Wait()
}
func loopPrint(n int, wait *sync.WaitGroup) {
for i := 0; i < n; i++ {
fmt.Println(i)
}
fmt.Println("over")
syncChan <- "ok"
wait.Done()
}
同时从channel中取值似乎也是阻塞的,参考如下代码,在goroutine中使用time.Sleep使得goroutine睡眠一定时间,同时主线程的取值也在等待
var syncChan = make(chan string)
func main() {
var wait sync.WaitGroup
wait.Add(1)
go loopPrint(10, &wait)
fmt.Println("i am main")
fmt.Println(<-syncChan)
fmt.Println("get the channel value", time.Now())
close(syncChan)
wait.Wait()
}
func loopPrint(n int, wait *sync.WaitGroup) {
for i := 0; i < n; i++ {
fmt.Println(i)
}
fmt.Println("over", time.Now())
time.Sleep(time.Second * 2)
syncChan <- "ok"
wait.Done()
}
/*output
i am main
0
1
2
3
4
5
6
7
8
9
over 2023-01-16 21:13:44.2619655 +0800 CST m=+0.003986801
ok
get the channel value 2023-01-16 21:13:46.2810119 +0800 CST m=+2.023033201
*/
Sync
互斥锁-sync.Mutex
使用互斥锁实现多线程或者多协程下同一资源的互斥访问。当一个共享代码块被加上互斥锁,只有一个goroutine可以访问,其他goroutine只有等使用者释放锁后才可以抢夺这个锁并去访问这个共享代码块,防止并发下产生的一些问题,参考如下加了互斥锁与未加互斥锁后结果
var LOCK sync.Mutex
var count = 0
var WAIT sync.WaitGroup
func main() {
WAIT.Add(4)
go countNum()
go countNum()
go countNum()
go countNum()
WAIT.Wait()
fmt.Println(count)
}
func countNum() {
LOCK.Lock()
for i := 0; i < 10000; i++ {
count++
}
LOCK.Unlock()
WAIT.Done()
}
/*output
加锁结果:40000
未加锁结果(将LOCK.Lock和LOCK.Unlock注释掉):30189
*/
单例模式/懒加载-sync.Once
通常情况下实现懒加载如下
type Instance struct {
data int
desc string
}
var INSTANCE *Instance
func getInstance() Instance {
if INSTANCE == nil {
INSTANCE = &Instance{0, "0"}
}
return *INSTANCE
}
但是这种在并发下会出现一些问题,比如两个线程获取到不同的实例,解决这个就需要进行加锁如下
func getInstance() Instance {
LOCK.Lock()
if INSTANCE == nil {
INSTANCE = &Instance{0, "0"}
}
LOCK.Unlock()
return *INSTANCE
}
但是这样直接加锁将对效率有着较大的影响,可以考虑使用双检测锁提高效率
func getInstance() Instance {
if INSTANCE == nil {
LOCK.Lock()
if (INSTANCE == nil) {
INSTANCE = &Instance{0, "0"}
}
LOCK.Unlock()
}
return *INSTANCE
}
但是容易写错(😅),所以,Go帮你做了个更好的,而且超简单
func getInstance() Instance {
LOAD_ONCE.Do(func() {
INSTANCE = &Instance{0, "0"}
})
return *INSTANCE
}
sync.Onc 是在代码运行中需要的时候执行,且只执行一次
func main() {
for i := 0; i < 100; i++ {
LOAD_ONCE.Do(func() {
fmt.Println("hello")
})
}
}
/*output
hello
*/
信号量-WaitGroup
在上面已经多次用到了,其实也非常简单,主要是三个方法
- Add(int) 添加指定值信号量
- Done() 将信号量减一
- Wait()阻塞直到信号量为0
参考: