并发concurrency
简单示例
- 任何一个函数在调用前加
go
便可以实现并发 - 注意如果不加任何通信,由于
go
所执行的协程并不会阻塞主函数的执行,主函数会同时继续向后执行,直到结束时退出。
func Hello(name string) {
fmt.Println("Hello", name)
}
func main() {
//一般情况下这段代码不会有任何输出,因为在调用输出函数之前main函数已经结束了
go Hello("Bob")
}
Channel
Goroutine 奉行通过通信来共享内存,而不是共享内存来通信。
创建channel
c := make(chan int)
使用channel
c <- 1 //向c中写入一个数字1
tmp := <-c //从c中读出一个数字并存到一个新变量tmp中
- channel是引用类型,传递参数传的是引用
- 读/写 无缓存 channel会造成阻塞
关闭channel
close(c)
示例改进
func Hello(c chan bool, name string) {
fmt.Println("Hello", name)
c <- true
}
func main() {
c := make(chan bool)
go Hello(c, "Bob")
<-c
}
遍历channel
- 遍历channel一定要注意能正确关闭channel,否则可能会发生死锁
for v := range c {
fmt.Println(v)
}
有缓存channel
- 区别
- 无缓存channel读写之间是同步的,是相互阻塞的,写入channel数据的操作会等待取出channel数据运行完之后才结束
- 有缓存channel读写是异步的,即写入操作只要成功写入后就结束,不管读取操作成功执行与否
c := make(chan int) //无缓存channel
d := make(chan int, 1) //有缓存channel,缓存容量为1
以下代码运行时将没有输出
func Hello(c chan bool, name string) {
fmt.Println("Hello", name)
<-c
}
func main() {
//此处如果去掉1或者把1改为0,就会变成同步操作
c := make(chan bool, 1)
go Hello(c, "Bob")
//异步通信,数据写入完就结束,不管其他协程是否取出数据
c <- true
}
一个多协程通信的例子
func Counter(c chan bool, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
c <- true
}
func main() {
t1 := time.Now()
//设置使用的CPU核心数
//runtime.GOMAXPROCS(runtime.NumCPU())
//c := make(chan bool, 10) //有缓存
c := make(chan bool) //无缓存
for i := 0; i < 10; i++ {
go Counter(c, i)
}
for i := 0; i < 10; i++ {
<-c
}
t2 := time.Now()
fmt.Println(t2.Sub(t1))
}
- 虽然一些教材上说
goruntime
默认运行在一个CPU核心上,但是考虑到教材内容无法与时俱进,经过自己尝试后发现是否添加runtime.GOMAXPROCS(runtime.NumCPU())
程序运行时间差异不大,而将参数设为1后会明显变慢,故猜测当前版本默认肯定不是单核心。具体解释需要在官方文档找到答案才好(原谅本菜鸟不知怎么找官方解释……)
Select
- 类似于
switch
语句,但case
都是channel的相关操作,用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作。 - 可处理一个或多个 channel 的发送与接收
- 同时有多个可用的 channel时按随机顺序处理
- 可用空的 select 来阻塞 main 函数
- 可设置超时
func main() {
c1, c2 := make(chan int), make(chan string)
o := make(chan bool)
go func() {
for {
select {
case v, ok := <-c1:
if ok == false {
o <- true
break
}
fmt.Println("c1", v)
case v, ok := <-c2:
if ok == false {
o <- true
break
}
fmt.Println("c2", v)
}
}
}()
c1 <- 1
c2 <- "Hello"
c1 <- 2
c2 <- "Test"
close(c1)
close(c2)
<-o
}
//一个用select随机生成01串的例子
func main() {
c := make(chan int)
go func() {
for v := range c {
fmt.Print(v)
}
}()
for i := 0; i < 10; i++ {
select {
case c <- 0:
case c <- 1:
}
}
close(c)
}
//在select中判断超时
func main() {
c := make(chan int)
select {
case v := <-c:
fmt.Println(v)
case <-time.After(3 * time.Second):
fmt.Println("Timeout")
}
close(c)
}
同步
*需要导入sync包
sync.WaitGroup
- 可以用其创建任务组
wg := sync.WaitGroup{} //创建一个任务组
wg.add(10) //增加10个任务
wg.Done() //完成一个任务
wg.Wait() //等待任务执行结束
- 由于是值拷贝,所以需要传入指针
一个多协程同步的例子
func Counter(wg *sync.WaitGroup, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
wg.Done()
}
func main() {
t1 := time.Now()
//runtime.GOMAXPROCS(runtime.NumCPU())
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go Counter(&wg, i)
}
wg.Wait()
t2 := time.Now()
fmt.Println(t2.Sub(t1))
}