Go channel有什么特点?
channel有2种类型:无缓冲、有缓冲
channel有3种模式:写操作模式(单向通道)、读操作模式(单向通道)、读写操作模式(双向通道)
写操作模式 | 读操作模式 | 读写操作模式 | |
---|---|---|---|
创建 | make(chan<- int) | make(<-chan int) | make(chan int) |
channel有3种状态:未初始化、正常、关闭
未初始化 | 关闭 | 正常 | |
---|---|---|---|
关闭 | panic | panic | 正常关闭 |
发送 | 永远阻塞导致死锁 | panic | 阻塞或者成功发送 |
接收 | 永远阻塞导致死锁 | 缓冲区为空则为零值, 否则可以继续读 | 阻塞或者成功接收 |
注意点:
- 一个 channel不能多次关闭,会导致painc
- 如果多个 goroutine 都监听同一个 channel,那么 channel 上的数据都可能随机被某一个 goroutine 取走进行消费
- 如果多个 goroutine 监听同一个 channel,如果这个 channel 被关闭,则所有 goroutine 都能收到退出信号
Go channel有无缓冲的区别?
无缓冲:一个送信人去你家送信,你不在家他不走,你一定要接下信,他才会走。
有缓冲:一个送信人去你家送信,扔到你家的信箱转身就走,除非你的信箱满了,他必须等信箱有多余空间才会走。
无缓冲 | 有缓冲 | |
---|---|---|
创建方式 | make(chan TYPE) | make(chan TYPE, SIZE) |
发送阻塞 | 数据接收前发送阻塞 | 缓冲满时发送阻塞 |
接收阻塞 | 数据发送前接收阻塞 | 缓冲空时接收阻塞 |
非缓冲 channel
package main
import (
"fmt"
"time"
)
func loop(ch chan int) {
for {
select {
case i := <-ch:
fmt.Println("this value of unbuffer channel", i)
}
}
}
func main() {
ch := make(chan int)
ch <- 1
go loop(ch)
time.Sleep(1 * time.Millisecond)
}
复制代码
这里会报错 fatal error: all goroutines are asleep - deadlock!
就是因为 ch<-1
发送了,但是同时没有接收者,所以就发生了阻塞
但如果我们把 ch <- 1
放到 go loop(ch)
下面,程序就会正常运行
缓冲 channel
package main
import (
"fmt"
"time"
)
func loop(ch chan int) {
for {
select {
case i := <-ch:
fmt.Println("this value of unbuffer channel", i)
}
}
}
func main() {
ch := make(chan int,3)
ch <- 1
ch <- 2
ch <- 3
ch <- 4
go loop(ch)
time.Sleep(1 * time.Millisecond)
}
复制代码
这里也会报 fatal error: all goroutines are asleep - deadlock! ,这是因为 channel 的大小为 3 ,而我们要往里面塞 4 个数据,所以就会阻塞住,解决的办法有两个:
- 把 channel 长度调大一点
- 把 channel 的信息发送者 ch <- 1 这些代码移动到 go loop(ch) 下面 ,让 channel 实时消费就不会导致阻塞了
Go channel为什么是线程安全的?
为什么设计成线程安全?
不同协程通过channel进行通信,本身的使用场景就是多线程,为了保证数据的一致性,必须实现线程安全
如何实现线程安全的?
channel的底层实现中,hchan结构体中采用Mutex锁来保证数据读写安全。在对循环数组buf中的数据进行入队和出队操作时,必须先获取互斥锁,才能操作channel数据
Go channel如何控制goroutine并发执行顺序?
多个goroutine并发执行时,每一个goroutine抢到处理器的时间点不一致,gorouine的执行本身不能保证顺序。即代码中先写的gorouine并不能保证先执行
思路:使用channel进行通信通知,用channel去传递信息,从而控制并发执行顺序
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
ch1 := make(chan struct{}, 1)
ch2 := make(chan struct{}, 1)
ch3 := make(chan struct{}, 1)
ch1 <- struct{}{}
wg.Add(3)
start := time.Now().Unix()
go print("gorouine1", ch1, ch2)
go print("gorouine2", ch2, ch3)
go print("gorouine3", ch3, ch1)
wg.Wait()
end := time.Now().Unix()
fmt.Printf("duration:%d\n", end-start)
}
func print(gorouine string, inputchan chan struct{}, outchan chan struct{}) {
// 模拟内部操作耗时
time.Sleep(1 * time.Second)
select {
case <-inputchan:
fmt.Printf("%s\n", gorouine)
outchan <- struct{}{}
}
wg.Done()
}
复制代码
- 输出:
gorouine1
gorouine2
gorouine3
duration:1
复制代码
Go channel共享内存有什么优劣势?
“不要通过共享内存来通信,我们应该使用通信来共享内存” 这句话想必大家已经非常熟悉了,在官方的博客,初学时的教程,甚至是在 Go 的源码中都能看到
无论是通过共享内存来通信还是通过通信来共享内存,最终我们应用程序都是读取的内存当中的数据,只是前者是直接读取内存的数据,而后者是通过发送消息的方式来进行同步。而通过发送消息来同步的这种方式常见的就是 Go 采用的 CSP(Communication Sequential Process) 模型以及 Erlang 采用的 Actor 模型,这两种方式都是通过通信来共享内存。
大部分的语言采用的都是第一种方式直接去操作内存,然后通过互斥锁,CAS 等操作来保证并发安全。Go 引入了 Channel 和 Goroutine 实现 CSP 模型将生产者和消费者进行了解耦,Channel 其实和消息队列很相似。而 Actor 模型和 CSP 模型都是通过发送消息来共享内存,但是它们之间最大的区别就是 Actor 模型当中并没有一个独立的 Channel 组件,而是 Actor 与 Actor 之间直接进行消息的发送与接收,每个 Actor 都有一个本地的“信箱”消息都会先发送到这个“信箱当中”。
优点
- 使用 channel 可以帮助我们解耦生产者和消费者,可以降低并发当中的耦合
缺点
- 容易出现死锁的情况
Go channel发送和接收什么情况下会死锁?
死锁:
- 单个协程永久阻塞
- 两个或两个以上的协程的执行过程中,由于竞争资源或由于彼此通信而造成的一种阻塞的现象。
channel死锁场景:
- 非缓存channel只写不读
- 非缓存channel读在写后面
- 缓存channel写入超过缓冲区数量
- 空读
- 多个协程互相等待
- 非缓存channel只写不读
func deadlock1() {
ch := make(chan int)
ch <- 3 // 这里会发生一直阻塞的情况,执行不到下面一句
}
复制代码
- 非缓存channel读在写后面
func deadlock2() {
ch := make(chan int)
ch <- 3 // 这里会发生一直阻塞的情况,执行不到下面一句
num := <-ch
fmt.Println("num=", num)
}
func deadlock2() {
ch := make(chan int)
ch <- 100 // 这里会发生一直阻塞的情况,执行不到下面一句
go func() {
num := <-ch
fmt.Println("num=", num)
}()
time.Sleep(time.Second)
}
复制代码
- 缓存channel写入超过缓冲区数量
func deadlock3() {
ch := make(chan int, 3)
ch <- 3
ch <- 4
ch <- 5
ch <- 6 // 这里会发生一直阻塞的情况
}
复制代码
- 空读
func deadlock4() {
ch := make(chan int)
// ch := make(chan int, 1)
fmt.Println(<-ch) // 这里会发生一直阻塞的情况
}
复制代码
- 多个协程互相等待
func deadlock5() {
ch1 := make(chan int)
ch2 := make(chan int)
// 互相等对方造成死锁
go func() {
for {
select {
case num := <-ch1:
fmt.Println("num=", num)
ch2 <- 100
}
}
}()
for {
select {
case num := <-ch2:
fmt.Println("num=", num)
ch1 <- 300
}
}
}
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付