概述
在这里不一定要去关闭channel,因为底层的垃圾回收机制会根据它是否可以访问来决定是否自动回收它。(这里不是根据channel是否关闭来决定的)
3.单向通道类型
当程序则够复杂的时候,为了代码可读性更高,拆分成一个一个的小函数是需要的。
此时go提供了单向通道的类型,来实现函数之间channel的传递。
上代码:
package main
import (
“fmt”
“time”
)
// 定义goroutine 1
func Echo(out chan<- string) { // 定义输出通道类型
time.Sleep(1*time.Second)
out <- “咖啡色的羊驼”
close(out)
}
// 定义goroutine 2
func Receive(out chan<- string, in <-chan string) { // 定义输出通道类型和输入类型
temp := <-in // 阻塞等待echo的通道的返回
out <- temp
close(out)
}
func main() {
echo := make(chan string)
receive := make(chan string)
go Echo(echo)
go Receive(receive, echo)
getStr := <-receive // 接收goroutine 2的返回
fmt.Println(getStr)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
程序输出:
咖啡色的羊驼
1
4.缓冲管道
goroutine的通道默认是是阻塞的,那么有什么办法可以缓解阻塞?
答案是:加一个缓冲区。
对于go来说创建一个缓冲通道很简单:
ch := make(chan string, 3) // 创建了缓冲区为3的通道
//=========
len(ch) // 长度计算
cap(ch) // 容量计算
1
2
3
4
5
6.goroutine死锁与友好退出
6.1goroutine死锁
来一个死锁现场一:
package main
func main() {
ch := make(chan int)
<- ch // 阻塞main goroutine, 通道被锁
}
1
2
3
4
5
6
输出:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
1
2
3
4
死锁现场2:
package main
func main() {
cha, chb := make(chan int), make(chan int)
go func() {
cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
chb <- 0
}()
<- chb // chb 等待数据的写
}
1
2
3
4
5
6
7
8
9
10
11
12
为什么会有死锁的产生?
非缓冲通道上如果发生了流入无流出,或者流出无流入,就会引起死锁。
或者这么说:goroutine的非缓冲通道里头一定要一进一出,成对出现才行。
上面例子属于:一:流出无流入;二:流入无流出
当然,有一个例外:
func main() {
ch := make(chan int)
go func() {
ch <- 1
}()
}
1
2
3
4
5
6
执行以上代码将会发现,竟然没有报错。
what?
不是说好的一进一出就死锁吗?
仔细研究会发现,其实根本没等goroutine执行完,main函数自己先跑完了,所以就没有数据流入主的goroutine,就不会被阻塞和报错
6.2goroutine的死锁处理
有两种办法可以解决:
1.把没取走的取走便是
package main
func main() {
cha, chb := make(chan int), make(chan int)
go func() {
cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
chb <- 0
}()
<- cha // 取走便是
<- chb // chb 等待数据的写
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2.创建缓冲通道
package main
func main() {
cha, chb := make(chan int, 3), make(chan int)
go func() {
cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
chb <- 0
}()
<- chb // chb 等待数据的写
}
1
2
3
4
5
6
7
8
9
10
11
12
这样的话,cha可以缓存一个数据,cha就不会挂起当前的goroutine了。除非再放两个进去,塞满缓冲通道就会了。
7.select的简介
定义:在golang里头select的功能与epoll(nginx)/poll/select的功能类似,都是坚挺IO操作,当IO操作发生的时候,触发相应的动作。
select有几个重要的点要强调:
1.如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行
上代码:
package main
import “fmt”
func main() {
ch := make (chan int, 1)
ch<-1
select {
case <-ch:
fmt.Println("咖啡色的羊驼")
case <-ch:
fmt.Println("黄色的羊驼")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
输出:
(随机)二者其一
1
2.case后面必须是channel操作,否则报错。
上代码:
package main
import “fmt”
func main() {
ch := make (chan int, 1)
ch<-1
select {
case <-ch:
fmt.Println(“咖啡色的羊驼”)
case 2:
fmt.Println(“黄色的羊驼”)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
输出报错:
2 evaluated but not used
select case must be receive, send or assign recv
1
2
3.select中的default子句总是可运行的。所以没有default的select才会阻塞等待事件
上代码:
package main
import “fmt”
func main() {
ch := make (chan int, 1)
// ch<-1 <= 注意这里备注了。
select {
case <-ch:
fmt.Println(“咖啡色的羊驼”)
default:
fmt.Println(“黄色的羊驼”)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
输出:
黄色的羊驼
1
4.没有运行的case,那么江湖阻塞事件发生报错(死锁)
package main
import “fmt”
func main() {
ch := make (chan int, 1)
// ch<-1 <= 注意这里备注了。
select {
case <-ch:
fmt.Println(“咖啡色的羊驼”)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
输出报错:
fatal error: all goroutines are asleep - deadlock!
1
8.select的应用场景
1.timeout 机制(超时判断)
package main
import (
“fmt”
“time”
)
func main() {
timeout := make (chan bool, 1)
go func() {
time.Sleep(1*time.Second) // 休眠1s,如果超过1s还没I操作则认为超时,通知select已经超时啦~
timeout <- true
}()
ch := make (chan int)
select {
case <- ch:
case <- timeout:
fmt.Println(“超时啦!”)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
以上是入门版,通常代码中是这么写的:
package main
import (
“fmt”
“time”
)
func main() {
ch := make (chan int)
select {
case <-ch:
case <-time.After(time.Second * 1): // 利用time来实现,After代表多少时间后执行输出东西
fmt.Println(“超时啦!”)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2.判断channel是否阻塞(或者说channel是否已经满了)
package main
import (
“fmt”
)
func main() {
ch := make (chan int, 1) // 注意这里给的容量是1
ch <- 1
select {
case ch <- 2:
default:
fmt.Println(“通道channel已经满啦,塞不下东西了!”)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
3.退出机制
package main
import (
“fmt”
“time”
)
func main() {
i := 0
ch := make(chan string, 0)
defer func() {
close(ch)
}()
go func() {
DONE:
for {
time.Sleep(1*time.Second)
fmt.Println(time.Now().Unix())
i++
select {
case m := <-ch:
println(m)
break DONE // 跳出 select 和 for 循环
default:
}
}
}()
time.Sleep(time.Second * 4)
ch<-"stop"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
输出:
1532390471
1532390472
1532390473
stop
1532390474
1
2
3
4
5
这边要强调一点:退出循环一定要用break + 具体的标记,或者goto也可以。否则其实不是真的退出。
package main
import (
“fmt”
“time”
)
func main() {
i := 0
ch := make(chan string, 0)
defer func() {
close(ch)
}()
go func() {
for {
time.Sleep(1*time.Second)
fmt.Println(time.Now().Unix())
i++
select {
case m := <-ch:
println(m)
goto DONE // 跳出 select 和 for 循环
default:
}
}
DONE:
}()
time.Sleep(time.Second * 4)
ch<-"stop"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
输出:
————————————————
版权声明:本文为CSDN博主「咖啡色的羊驼」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011957758/article/details/81159481
最后
以上就是勤奋仙人掌为你收集整理的GoRoutine的全部内容,希望文章能够帮你解决GoRoutine所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复