烂笔头啊烂笔头......
golang是最好的并发语言,拒绝任何反驳。
说到golang的并发,必须要提一个概念--协程
开销:进程>线程>协程 三者之间的关系可自行百度
目前并发比较流行的三种模式:
1、多线程:每个线程一次处理一个请求,线程越多可并发处理的请求数就越多,但是在高并发下,多线程开销会比较大。
2、协程:无需抢占式的调度,开销小,可以有效的提高线程的并发性,从而避免了线程的缺点的部分。
3、基于异步回调的IO模式,说个比较流行的快速建站语言--nodejs,通过事件驱动的方式与异步IO回调,使得服务器持续运转,来支撑高并发的请求
说正题.....
goroutine
goroutine就是golang中的协程,也可以叫做轻量级的现成,与线程比较,创建的成本以及开销都小很多,每个goroutine的堆栈只有几kb,并且堆栈可以根据程序的需要增长和缩小,而线程的堆栈是需要指明和固定的。所以golang从语言层面上支持高并发。
创建格式 go func()
goroutine正确的使用姿势一:主协成结束时,其他协程都会强制结束!
package main
import (
"fmt"
"time"
)
func out() {
fmt.Println("我想先出来")
}
func main() {
go out()
time.Sleep(1 * time.Second)
fmt.Println("好吧,看在sleep哥的份上,让你先出来")
}
执行结果:
我想先出来
好吧,看在sleep哥的份上,让你先出来
如果把time.Sleep注释了,main就不会再等其他协程执行完了,你可能就看不到out的输出了。
goroutine正确的使用姿势二:不同的goroutine之间不会相互影响
package main
import (
"fmt"
"strconv"
"time"
)
func PrintMany() {
for i := 1; i <= 3; i++ {
time.Sleep(250 * time.Millisecond)
fmt.Println("打印第" + strconv.Itoa(i) + "个")
}
}
func PrintSingle() {
fmt.Println("我就打印一个")
}
func main() {
go PrintMany()
go PrintSingle()
time.Sleep(2 * time.Second)
fmt.Println("我是最后一个")
}
执行结果:
我就打印一个
打印第1个
打印第2个
打印第3个
我是最后一个
PrintMany里面有Sleep,是否会导致第二个goroutine阻塞或者等待呢?no。因为两个goroutine之间不会相互影响,main会继续按顺序执行下去
goroutine伴侣--channel
回到姿势一中的例子,把Sleep注释后,你可能就看不见go out()的输出了。因为main非常之霸道,这时候就需要channel去阻止它。
package main
import (
"fmt"
)
var bol chan bool
func One() {
fmt.Println("I am One")
bol <- true
}
func main() {
bol = make(chan bool)
go One()
<-bol
fmt.Println("我又要最后一个出来了")
}
执行结果:
I am One
我又要最后一个出来了
<-bol 能起到了阻塞作用。
对channe阻塞的理解:
发送者角度:对于同一个通道,发送操作(协程或者函数中的),在接收者准备好之前是阻塞的。如果chan中的数据无人接收,就无法再给通道传入其他数据。因为新的输入无法在通道非空的情况下传入。所以发送操作会等待 chan 再次变为可用状态:就是通道值被接收时(可以传入变量)。
package main
import (
"fmt"
)
var bol chan bool
func One() {
fmt.Println("I am One")
bol <- true
}
func main() {
bol = make(chan bool)
go One()
bol <- false //bol中的数据还未取出,无法再传入数据,所以会报错:deadlock!
fmt.Println("我又要最后一个出来了")
}
接收者角度:对于同一个通道,接收操作是阻塞的(协程或函数中的),直到发送者可用:如果通道中没有数据,接收者就阻塞了。
package main
import (
"fmt"
)
var bol chan bool
func One() {
fmt.Println("I am One")
}
func main() {
bol = make(chan bool)
go One()
<- bol //deadlock!
fmt.Println("我又要最后一个出来了")
}
Channel类型的三种定义格式:
chan int //既可以发送数据也可以接受数据
<- chan int //只允许从chan接受int类型数据
chan <- int //只允许发送int类型数据到chan
Channel默认是阻塞的。如果要“缓解”阻塞,就要给channel加一个缓冲区。
ch := make(chan string, 3) // 创建了缓冲区为3的通道
//=========
len(ch) // 长度计算 计算缓冲区已填充的长度
cap(ch) // 容量计算 计算缓冲区的长度
select关键字用法
select和常用的switch用法类似,但select只用来监听和channel有关的IO操作,当IO操作发生时,触发相应的动作。select的case机会均等,既如果多个case都满足运行条件,则select会随机选出一个执行,其他不会执行。没有default的select有几率发生死锁事件。
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("超时啦!")
}
}
//====================
package main
import (
"fmt"
"time"
)
func main() {
ch := make (chan int)
select {
case <-ch:
case <-time.After(time.Second * 1): // 利用time来实现,After代表多少时间后执行输出东西;After返回一个Time类型的只读channel
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已经满啦,塞不下东西了!")
}
}