写在前面
并发 Concurrency
- go的并发是经过goroutine来实现的
- 它的使用,就是在某个操做前加上go关键字
go f(x, y, z)
- 什么叫当前的goroutine?
package main
import "fmt"
func main() {
fmt.println("hello,world!")
}
- 若是程序开始执行,那么上面的的就是当前的goroutine了
- 什么是非当前的goroutine
import "fmt"
func NewGoroutine() {
fmt.Println("new goroutine!")
}
func main() {
go NewGoroutine() //这里就添加了新的goroutine了(运行起来才算)
fmt.println("hello,world!")
}
- 可是,运行起来并无看到打印出"new goroutine"
- 这是由于当前的goroutine并无义务等待这个新的goroutine执行,它本身执行完就结束了
- 那么应该如何改变呢?Go提供了一些列管理goroutine的方法,其中最主要就是channel和Mutex了
Channel
- Channel也是一种类型,它的定义和使用比较特别
ch := make(chan int)
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and
// assign value to v.
- 定义通常采用make函数进行,chan关键字是固定的,后面该channel能够放置的类型,例子中的就是int
- 它的基本使用有两种模式:
- 其余变量往channel传送数据:ch<-v
- 其余变量从channel接受数据:v := <-ch
- 两种操做是互相等待的,以例子中的说明
- 当A处的数据创送给ch时(ch <- A),它会先判断,是否其它地方须要从ch中获取数据
- 如果:则数据从A处传到指定的地方
- 若不是:A处的goroutine将被阻塞(其实也就是停下来),等到其它任意地方须要从ch中获取数据时,A处的goroutine才开始运行
- 而B处须要从ch获取数据(B := <-ch),它会先判断,是否某个地方给ch传送数据
- 如果:则数据从某个地方传送到B处
- 若不是:B处的goroutine将被阻塞(其实也就是停下来),等到其它任意地方传送数据给ch,B处的goroutine才开始运行
- 简单的比喻就是传送门两侧,须要等待两边的人都赞成才能打开
- 或者这样理解,若是把这个过程比喻成接力赛
- 接力棒是数据,每一个队有多个成员
- A成员的目标就是将接力棒交给B成员,但须要奔跑,须要时间,B成员须要等待A成员(被阻塞)
- 下面是例子代码:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
- 读者们能够试着把ch定义成普通的channel:ch := make(chan int)
- 这样会报错,缘由在于:
- 首先这是在一个goroutine中
- 其次,ch <-1执行时,会一直等待其它goroutine执行从ch中拿数据的操做
关键字:range,close
- 当某个goroutine中的某个操做须要不停从channel c中获取数据时,能够用到range关键字
- 而对于上述操做,并不须要担忧当channel c再也不有数据的事情,这个事情是由运行发送数据到channel c的goroutine关心的
- 若是发送数据给channel c的goroutine不须要再发送数据了,那就须要执行close(c)的操做,不然程序会报错
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
- 对于上述代码,先关注main函数
- go fibonacci(cap(c), c)这一句新建了一个goroutine,其中cap(c)是用来计算channel c的容量
- for i := range c这句,
- 将不停循环判断,
- 一有数据传送到channel c,i 就能获取该数据,并执行循环语句中的内容
- 当channel c被关闭,循环判断将结束
- 再看fibonacci函数
- fibonacci数列的计算,其中for循环里的内容c <- x这句,就是向channel c传送数据,以后main中的for语句会执行相应操做
- 最后close(c)这句,关闭channel c,以后main的for语句结束执行
关键字 select
- select很特别,它通常会搭配for循环使用,行为上和switch语句相似
- select语句判断是是否执行了某些操做,若是是,我就将执行XXX操做
- switch语句判断的一般是某某值是否等于某某值这种true or false的问题
- 代码:
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
- main()中,建立了channel c和channel quit
- 一个匿名函数做为新建goroutine的运行内容
- 而后运行fibonacci函数(这是当前goroutine中的)
- 在fibonacci函数中,使用for进行无限循环不停执行select语句
- 在这里select语句会不停判断每一个case语句中的操做
- 像case c <- x,判断条件也是一种操做,因此会先把x传送给c,而后阻塞,等待其余向channel c要数据的操做
- 这时,匿名函数(新建的goroutine)的for循环中fmt.Println(<-c)将会执行,执行完成后,又会进行等待其余操做向channel c 传递数据
- 而在fibonacci中,case c <- x能顺利进行后,将执行x, y = y, x+y,而后进入新的循环中执行select语句
- 过程就像上述那样,进行循环,判断,阻塞,解除阻塞。。。
- 等到goroutine的循环结束,并执行quit <- 0后,就会执行select语句中的case <-quit的内容了,以后程序返回,当前goroutine结束
- 固然,select也会搭配关键字default:
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
- 当其它case中的channel都每准备好,就会执行default中的语句
sync.Mutex
- 解决并发冲突的办法还有比较经典的Mutex
- Mutex能够进行加锁Lock()和解锁操做Unlock()
- 这里不展开了,留在下一篇,也是a tour of go的最后一道习题,我的认为这个题目出得很是好,值得详细讲解