1.从并发模型说起
定义:在go里面,每一个并发执行的活动成为goroutine。
详解:goroutine可以认为是轻量级的线程,与创建线程相比,创建成本和开销都很小,每个goroutine的堆栈只有几kb,并且堆栈可根据程序的需要增长和缩小(线程的堆栈需指明和固定),所以go程序从语言层面支持了高并发。
程序执行的背后:当一个程序启动的时候,只有一个goroutine来调用main函数,称它为主goroutine,新的goroutine通过go语句进行创建。
1.2多个线程创建
package main
import (
"fmt"
"time"
)
func DelayPrint() {
for i := 1; i <=5; i++ {
time.Sleep(300 * time.Millisecond)
fmt.Println(i)
}
}
func HelloWorld() {
fmt.Println("Hello world goroutine")
}
func main() {
go DelayPrint() // 开启第一个goroutine
go HelloWorld() // 开启第二个goroutine
time.Sleep(2*time.Second)
fmt.Println("main function")
}
函数输出:
Hello world goroutine
1
2
3
4
5
6
main function
2.通道(channel)
2.1 无缓冲通道
说明:无缓冲通道上的发送操作将会被阻塞,直到另一个goroutine在对应的通道上执行接收操作,此时值才传送完成,两个goroutine都继续执行。
package main
import (
"fmt"
"time"
)
var done chan bool
func HelloWorld() {
fmt.Println("Hello world goroutine")
time.Sleep(2*time.Second)
done <- true
}
func main() {
done = make(chan bool) // 创建一个channel
go HelloWorld()
<-done
}
输出
Hello world goroutine
2.2 管道
可以用来链接goroutine,这样一个输出是另一个输入
例子:
package main
import (
"fmt"
"time"
)
var echo chan string
var receive chan string
// 定义goroutine 1
func Echo() {
time.Sleep(1*time.Second)
echo <- "游戏人生"
}
// 定义goroutine 2
func Receive() {
temp := <- echo // 阻塞等待echo的通道的返回
receive <- temp //temp赋值给receive
}
func main() {
echo = make(chan string)
receive = make(chan string)
go Echo()
go Receive()
getStr := <-receive // 接收goroutine 2的返回
fmt.Println(getStr)
}
在这里不一定要去关闭channel,因为底层的垃圾回收机制会根据它是否可以访问来决定是否自动回收它。(这里不是根据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)
}
输出:
游戏人生
2.3 缓冲管道
goroutine的通道默认是是阻塞的,那么有什么办法可以缓解阻塞?
答案是:加一个缓冲区。
对于go来说创建缓冲通道非常简单:
ch := make(chan string, 3) // 创建了缓冲区为3的通道
len(ch) // 长度计算
cap(ch) // 容量计算
3.goroutine死锁与友好退出
3.1goroutine死锁
非缓冲通道上如果发生了流入无流出,或者流出无流入,就会引起死锁。
例子1.
package main
func main() {
ch := make(chan int)
<- ch // 阻塞main goroutine, 通道被锁
}
输出:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
问题为:流出无流入
例子2
package main
func main() {
cha, chb := make(chan int), make(chan int)
go func() {
cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
chb <- 0
}()
<- chb // chb 等待数据的写
}
流入无流出
goroutine的非缓冲通道里头一定要一进一出,成对出现才行。
3.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 等待数据的写
}
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 等待数据的写
}
这样的话,cha可以缓存一个数据,cha就不会挂起当前的goroutine了。除非再放两个进去,塞满缓冲通道就会了。
4.select的简介
当IO操作发生的时候,触发相应的动作。
介绍一下简单应用
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("超时啦!")
}
}
以上是入门版,通常代码中是这么写的:
package main
import (
“fmt”
“time”
)
func main() {
ch := make (chan int)
select {
case <-ch:
case <-time.After(time.Second * 1): // 利用time来实现,After代表多少时间后执行输出东西
fmt.Println(“超时啦!”)
}
}
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已经满啦,塞不下东西了!")
}
}
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"
}
输出:
1532390471
1532390472
1532390473
stop
1532390474
这边要强调一点:退出循环一定要用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"
}
输出:
1532390525
1532390526
1532390527
1532390528
stop