多线程问题
当多个协程访问同一个资源时,则会出现资源竞争,那么怎么保证并发线程安全,这时候则可以引出channel
为什么需要channel
- 前面使用全局变量加锁同步来解决 goroutine的通讯,但不完美
- 主线程在等待所有 goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算
- 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有 goroutine处于工作状态,这时也会随主线程的退出而销毁
- 全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作
channel介绍
本质是队列,先进先出,线程安全,多个goroutine访问channel线程安全
使用
- channel是引用类型
- 必须初始化(make)之后才能使用,
- 管道有类型
使用close可以关闭channel,这个时候再向其写数据时会报错 panic: send on closed channel,但是可以继续读数据
for-range遍历管道,如果管道未关闭,会出现死锁,如果管道关闭了。则会正常退出循环
未关闭会出现死锁
var intCh chan int
intCh = make(chan int, 3)
intCh <- 10
intCh <- 11
intCh <- 12
for i := range intCh {
fmt.Println(i)
}
输出:
10
11
12
fatal error: all goroutines are asleep - deadlock!
关闭.正常退出
var intCh chan int
intCh = make(chan int, 3)
intCh <- 10
intCh <- 11
intCh <- 12
close(intCh)
for i := range intCh {
fmt.Println(i)
}
输出:
10
11
12
同时读写
var (
ch = make(chan int, 50)
flag = true
)
func main() {
go write()
go read()
for flag {
}
}
func read() {
for {
a, ok := <-ch
if ok {
fmt.Println(a)
} else {
break
}
}
}
func write() {
for i := 0; i < 50; i++ {
ch <- i
fmt.Println("write", i)
time.Sleep(time.Second)
}
close(ch)
flag = false
}
输出结果:
write 0
0
write 1
1
write 2
2
write 3
3
write 4
4
write 5
5
write 6
6
可以看到,写和读是交替进行的,当管道写完数据,关闭管道后,flag为false,主线程退出
无缓冲的管道
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)
go recv(ch) // 启用goroutine从通道接收值
ch <- 10
fmt.Println("发送成功")
}
如果创建一个无缓冲的管道,向管道中发送数据,必须有接收才能发送。否则会出现死锁
单向通道
限制函数中通道只能接受或者只能发送
func counter(out chan<- int) {
for i := 0; i < 100; i++ {
out <- i
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for i := range in {
out <- i * i
}
close(out)
}
func printer(in <-chan int) {
for i := range in {
fmt.Println(i)
}
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go counter(ch1)
go squarer(ch2, ch1)
printer(ch2)
}
chan<- int是一个只写单向通道(只能对其写入int类型值),可以对其执行发送操作但是不能执行接收操作;<-chan int是一个只读单向通道(只能从其读取int类型值),可以对其执行接收操作但是不能执行发送操作。
在函数传参及任何赋值操作中可以将双向通道转换为单向通道,但反过来是不可以的
select方式从未关闭管道取数据
当管道中有数据会走case,全部取完之后则走default
func main() {
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
intChan <- i
}
strChan := make(chan string, 5)
for i := 0; i < 5; i++ {
strChan <- "hello" + string(rune(i))
}
label:
for {
select {
case v := <-intChan:
fmt.Println(v)
case s := <-strChan:
fmt.Println(s)
default:
break label
}
}
}
使用goroutine注意事项
一旦程序出现panic,整个程序都会崩溃,所以可以使用defer加匿名函数做处理
func test() {
defer func() {
if r := recover(); r != nil {
fmt.Println("test 错判我")
}
}()
var myMap map[int]string
myMap[0] = "111"
}