并发concurrency
- 很多人都是冲着Go大肆宣扬的高并发而忍不住跃跃欲试,但其实从源码的解析来看,goroutine只是由官方实现的“线程池”而已。不过话说回来,每个实例4-5kb的栈内存占用和由于实现机制而大幅减少的创建和销毁开销,是制造Go号称高并发的根本原因。另外,goroutine的简单易用,也在语言层面上给予了开发者巨大的便利。
并发不是并行:Concurrency Is Not Parallelism
- 并发主要有切换时间片来实现“同时”运行,在并行则是直接利用多核实现多线程的运行,但Go可以设置使用核数,以发挥多核计算机的能力。
Goroutine奉行通过通信来共享内存,而不是共享内存来通信。
/*一个最最最最基本的goroutine*/
package main
import (
"fmt"
"time"
)
func main() {
go Go()
time.Sleep(2 * time.Second)
}
func Go() {
fmt.Println("Go Go Go!!!")
}
Channel
- Channel 是 goroutine沟通的桥梁,大都是阻塞同步的
- 通过make创建,close关闭
- Channel是引用类型
- 可以使用for range来迭代不断操作channel
- 可以设置单项或双向通道
- 可以设置缓存大小,在被填满前不会发生阻塞
package main
import (
"fmt"
)
func main() {
c := make(chan bool) //make(chan [所存储的数据类型])
go func() {
fmt.Println("Go Go Go!!!")
c <- true //把true给到c
}()
<-c //把c取出来的操作,在这里会等待并发函数把数据给到c之后才执行,保证了并发线程结束后主线程才结束
}
package main
import (
"fmt"
)
func main() {
c := make(chan bool) //make(chan [所存储的数据类型]),直接make的都是双向通道,又可以存又可以取
go func() {
fmt.Println("Go Go Go!!!")
c <- true //把true给到c
close(c)
}()
for v := range c {
fmt.Println(v)
}//一定要注意在某处把channel关闭掉,否则会成为死锁,无限等待
}
//这个函数的流程是这样的:go关键字并发了一个匿名函数,接着执行for-range,此时for-range的执行需要等待一个c,在匿名函数中,true给到c之后,立刻for-range可以执行了,输出了这个v(v就是c),并且立刻又进入等待下一个c的状态,在匿名函数中,此时关闭了c,for-range立刻接收到了c被关闭的信号,整个函数结束。
运行结果:
Go Go Go!!!
true
channel分为有缓存和无缓存
无缓存的channel
package main
import (
"fmt"
)
func main() {
c := make(chan bool) //这里为无缓存的channel
go func() {
fmt.Println("Go Go Go!!!")
c <- true
}()
<-c //运行到这里的时候该取了,可实际还没有放进去,所以在等待,等待放入之后再执行取的操作,无缓存是阻塞的,c里的东西不被读掉,程序无法往下执行
}
把channel的存读操作对调一下
package main
import (
"fmt"
)
func main() {
c := make(chan bool) //这里为无缓存的channel
go func() {
fmt.Println("Go Go Go!!!")
<-c
}()
c <- true //无缓存是阻塞的,c里的东西不被读掉,程序无法往下执行
}
以上两次操作均可以输出Go Go Go!!!
原因就在于,无缓存的channel是阻塞的。
有缓存的channel
package main
import (
"fmt"
)
func main() {
c := make(chan bool, 1) //这就成了有缓存的channel了
go func() {
fmt.Println("Go Go Go!!!")
<-c
}()
c <- true
}
//这个程序不会输出Go Go Go!!!
//因为有缓存,所以channel的存和取的操作不是堵塞的,简单说,存了以后,爱读不读。程序都会往下接着走,读会等待,取不会等待哦!
所以调换c的存取操作顺序就又可以输出Go Go Go!!!了
package main
import (
"fmt"
)
func main() {
c := make(chan bool, 1) //这就成了有缓存的channel了
go func() {
fmt.Println("Go Go Go!!!")
c <- true
}()
<-c
}
一个经典的多核并发程序
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) //设置使用的CPU核数(获取本台计算机的CPU核数)
c := make(chan bool)
for i := 0; i < 10; i++ {
go Go(c, i)
} //当一个核的时候还是按部就班的一个线程一个线程开始执行,可是现在变成多核以后,指不定先执行哪个后执行哪个,可能先执行i=5的,也可能先执行i=8的,即分配任务不定的特性
<-c
}
func Go(c chan bool, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
if index == 9 {
c <- true //因此这个地方就错误了,有时候index=9并不是最后一个执行的。
}
}
//输出的结果是不确定的,因为执行index=9的次序是不确定的。
那么如何解决这个问题呢?
解决方案1—-利用缓存
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool, 10) //缓存个数为10
for i := 0; i < 10; i++ {
go Go(c, i)
} //执行了10个线程
for i := 0; i < 10; i++ {
<-c
} //在这里取10次
}
func Go(c chan bool, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
c <- true //在这里存一次
}
解决方案2—-利用WaitGroup(在”sync”包里)
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
wg := sync.WaitGroup{}
wg.Add(10) //设置增加10个任务量
for i := 0; i < 10; i++ {
go Go(&wg, i) //channel是引用类型,但是此处wg不是引用类型,所以要把它地址传进去
}
wg.Wait() //在这里要等待任务都完成
}
func Go(wg *sync.WaitGroup, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
wg.Done() //标记一次done
}
Select
- 可处理一个或多个channel的发送与接收
- 同时又多个可用的channel的按随机顺序处理
- 可用空的select来阻塞main函数
- 可设置超时
select是Go中的一个控制结构,类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。
select随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
以下描述了 select 语句的语法:
每个case都必须是一个通信
所有channel表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行;其他被忽略。
如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
否则:
1、如果有default子句,则执行该语句。
2、如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
实例
package main
import "fmt"
func main() {
var c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1\n")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
fmt.Printf("received ", i3, " from c3\n")
} else {
fmt.Printf("c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
}
以上输出结果如下:
no communication
如何设置超时
//设置超时
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan bool)
select {
case v := <-c:
fmt.Println(v)
case <-time.After(3 * time.Second):
fmt.Println("Timeout")
//可以设置超时,即超过多少时间还没有接收到任务就执行的语句
}
}
输出结果:
[ `go run temp.go` | done: 3.2158144s ]
Timeout
有疑问加站长微信联系(非本文作者)