28、向nil通道(channel)读写数据会panic吗?
package main 1、//从一个nil通道读取报fatal error: all goroutines are asleep - deadlock! func main1() { //var receiveChan <-chan interface{} //var sendChan chan<- interface{} //dataStream := make(chan interface{}) //receiveChan = dataStream //sendChan = dataStream var dataStream chan interface{} <-dataStream } 2、//向nil管道写入数据报fatal error: all goroutines are asleep - deadlock! func main2() { var dataStream chan interface{} dataStream <- struct{}{} } 3、//关闭nil关闭会报错panic: close of nil channel func main() { var dataStream chan interface{} close(dataStream) }
29、什么是协程泄露(Goroutine Leak)?
Go的并发是以goroutine和channel的形式实现的。协程泄露是指goroutine创建后,长时间得不到释放,并且还在不断地创建新的goroutine协程,最终导致内存耗尽,程序崩溃。
常见的导致goroutine泄露的场景:
- 缺少接收器,导致发送阻塞
例如,启动n个协程去接收信道中的信息,但是信道并不会发送n次那么多信息。从而导致接收协程阻塞,不能退出。
package main import ( "fmt" "math/rand" "runtime" "sync" "sync/atomic" "time" ) func random() int { n := rand.Intn(100) time.Sleep(time.Duration(n) * time.Millisecond) return n } func querySend() int { ch := make(chan int) go func() { ch <- random() }() go func() { ch <- random() }() return <-ch } func main() { for i := 0; i < 3; i++ { querySend() fmt.Println("在运行中的goruntines:", runtime.NumGoroutine()) } }
- 死锁(dead lock)
- 同一个goroutine中,使用同一个chnnel读写;
- 2个 以上的go程中, 使用同一个 channel 通信。 读写channel 先于 go程创建;
- channel 和 读写锁、互斥锁混用;
func main2() { ch := make(chan int) ch <- 1 //这里一直阻塞,运行不到下面 <-ch }
- 无限死循环(infinite loops)
I/O 操作上的堵塞也可能造成泄露,例如发送请求到 API 服务器,而没有使用超时;或者程序单纯地陷入死循环中。
30、Go中深拷贝和浅拷贝
一、概念:
go语言的值类型复制都是深拷贝,引用类型一般都是浅拷贝。深浅拷贝的本质,就是看拷贝内容是数据还是数据的地址。
1)深拷贝(Deep Copy):
拷贝的数据,拷贝时创建一个新对象,开辟一个新的内存空间,把原对象的数据复制过来,新对象修改数据时不会影响原对象的值。既然数据内存地址不同,释放内存地址时,需要分别释放。
2)浅拷贝(Shallow Copy):
拷贝的是数据地址,拷贝时创建一个新对象,然后复制指向的对象的指针。此时新对象和原对象指向的地址都是一样的,因此,新对象修改数组时,会影响原来对象。
注意:值类型的数据拷贝默认都是深拷贝,引用类型的数据拷贝一般是浅拷贝。
二、演示:
示例1:深拷贝(拷贝数据)
type student struct { name string age int } func main() { fmt.Println("演示浅拷贝,内存地址是相同的") stu1 := student{ name: "深1", age: 1, } stu2 := stu1 fmt.Printf("stu1的地址:%p,值:%v\n", &stu1, stu1) fmt.Printf("stu2的地址:%p,值:%v\n", &stu2, stu2) stu2.name = "深2" fmt.Printf("stu1的地址:%p,值:%v\n", &stu1, stu1) fmt.Printf("stu2的地址:%p,值:%v\n", &stu2, stu2) } 结果:演示浅拷贝,内存地址是相同的 stu1的地址:0xc000004078,值:{深1 1} stu2的地址:0xc000004090,值:{深1 1} stu1的地址:0xc000004078,值:{深1 1} stu2的地址:0xc000004090,值:{深2 1}
示例2:浅拷贝 (拷贝指针)
type student struct { name string age int } func main() { fmt.Println("演示浅拷贝,内存地址是相同的") stu1 := new(student) { stu1.name = "浅1" stu1.age = 1 } stu2 := stu1 fmt.Printf("stu1的地址:%p,值:%v\n", stu1, stu1) fmt.Printf("stu2的地址:%p,值:%v\n", stu2, stu2) stu2.name = "浅2" fmt.Printf("stu1的地址:%p,值:%v\n", stu1, stu1) fmt.Printf("stu2的地址:%p,值:%v\n", stu2, stu2) } 演示浅拷贝,内存地址是相同的 stu1的地址:0xc000004078,值:&{浅1 1} stu2的地址:0xc000004078,值:&{浅1 1} stu1的地址:0xc000004078,值:&{浅2 1} stu2的地址:0xc000004078,值:&{浅2 1}
31、go中CAS
CAS(compare and swap)有道词典CAS(比较和交换)
- go中CAS操作具有原子性,在解决多线程操作共享变量安全上可以有效的减少使用锁所带来的开销,但是这是使用cpu资源做交换的
- go中的Cas操作与java中类似,都是借用了CPU提供的原子性指令来实现。CAS操作修改共享变量时候不需要对共享变量加锁,而是通过类似乐观锁的方式进行检查,本质还是不断的占用CPU