并发concurrency
goroutine只是由官方实现的超级”线程池”,每个实例4-5kb的栈内存占用和由于实现机制而大幅减少的创建和销毁开销是造成golang高并发的根本原因。
并发并不是并行: concurrency is not parallelism
并发主要由切换时间片来实现”同时”运行,在并行则是直接利用多核心实现多线程的运行。Go可以设置使用核心数量,以发挥多核心计算机的能力。
Goroutine奉行通过通信
来共享内存,而不是共享内存来通信。
Channel
- channel是goroutine沟通的桥梁,大部分是阻塞同步的
- 通过make创建,close关闭
func main() {
//创建一个channel
c := make(chan bool)
//使用goroutine操作匿名函数
go func() {
fmt.Println("go biaoge!")
//想channel中存值
c <- true
}()
//从channel c中取值
<- c
}
- channel是引用类型
- 可以使用for range来结合close函数来操作channel
func main() {
c := make(chan bool)
go func() {
fmt.Println("go biaoge!")
c <- true
//每次完成后进行关闭channel;否则的话会变成死锁;所以在使用channel的时候,一定需要再一个地方显式的关闭channel
close(c)
}()
//<- c
//使用range来获取channel中的值
for v := range c{
fmt.Println(v)
}
}
可以设置单向或双向通道
双向通道一般是既能存值,又能取值 c := make(chan bool)
可以设置缓存大小,在未被填满前不会发生阻塞
缓存为0的话就是阻塞的
没有缓存的channel,取的操作先于放的操作
// 无缓存的channel
a := make(chan bool)
go func() {
fmt.Println("go biaoge!")
<- a
close(c)
}()
a <- true
//
有缓存的channel:放的操作先与取的操作
//有缓存的channel
e := make(chan bool,1)
go func() {
fmt.Println("1231")
e <- true
}()
<- e
总结:
- 有缓存的是异步的
- 无缓存的是同步阻塞的
保证并发过程中能够执行完成所有的任务,第一种方式是使用有缓存的channel,第二种方式是使用sync
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool,10)
for i :=0;i < 10;i++ {
go Go(c,i)
//<- c
}
//通过有缓冲的channel来实现多核心的高并发
for i := 0;i < 10;i++ { <- c }
}
func Go(c chan bool,index int) {
a := 1
for i :=0 ;i < 1000000;i++ {
a += i
}
fmt.Println(index,a)
c <- true
}
package main
import (
"fmt"
"runtime"
"sync"
)
//使用sync包中的WaitGroup{}来实现任务的等待
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
wg := sync.WaitGroup{}
wg.Add(10)
for i :=0;i < 10;i++ {
go Go(&wg,i)
}
wg.Wait()
}
func Go(wg *sync.WaitGroup,index int) {
a := 1
for i :=0 ;i < 1000000;i++ {
a += i
}
fmt.Println(index,a)
//好像是每Done()一次,就会在wg中减一次
wg.Done()
}
Select
- 可以处理一个或多个channel的发送和接收
package main
import (
"fmt"
)
func main() {
c1,c2 := make(chan int),make(chan string)
//因为是使用goroutine的,因此无法确定c1和c2的存取顺序,如果最后那个c2优先执行了,那么整个程序也会因为main函数的结束而关闭。因此这个时候需要再创建一个channel来判断整个goroutine中的任务十分完成
done := make(chan bool)
go func() {
for {
select {
case v,ok := <- c1:
if !ok {
//如果c1 或者c2没有取成功,则表示任务执行完成,往done的通道里存值
done <- true
break
}
fmt.Println("c1",v)
case v,ok := <- c2:
if !ok {
done <- true
break
}
fmt.Println("c2",v)
}
}
}()
c1 <- 1
c2 <- "test-biaoge"
c1 <- 100
c2 <- "bgops"
//一定要显式关闭channel
close(c1)
close(c2)
//取出done通道里的内容来判断任务的执行完成
<- done
}
需要注意的是,如果channel是有零值的,可能在误关闭channel的时候导致不断读取零值
使用select进行存取
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for v:=range c{
fmt.Println(v)
}
}()
for i :=0;i < 10 ;i++ {
//使用select对channel进行写入操作
select {
case c <- 0:
case c <- 1:
}
}
}
- 同时又多个可用的channel时按随机顺序处理
- 可用空的select来阻塞main函数(事件循环)
- 可设置超时
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan bool)
select {
//去c里面读取值
case v := <- c:
fmt.Println(v)
//读取超时
//time.After()返回的也是一个chan
case <- time.After(3*time.Second):
fmt.Println("Channel Timeout")
}
}
header 1 | header 2 |
---|---|
ABC | aac abc |