Files
build-web-application-with-…/2.7.md
JessonChan 3fa30ad8b8 通顺语句
见diff
2012-10-14 15:20:26 +08:00

193 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#2.7 并发
有人把Go比作21世纪的C语言第一是因为Go语言设计简单第二21世纪最重要的就是并行程序设计而GO从语言层面就支持了并行。
##Goroutines
Goroutines是Go并行设计的核心。Goroutines说到底其实就是线程但是他比线程更小十几个Goroutines可能体现在底层就是五六个线程Go语言内部帮你实现了这些Goroutines之间的内存共享。Go语言的作者经常说着这样一句话不要通过共享来通信而要通过通信来共享。
Goroutines是通过Go的runtime管理的一个线程管理器。Goroutines通过`go`关键字实现了,其实就是一个普通的函数。
go hello(a, b, c)
通过关键字go就启动了一个Goroutines。我们来看一个例子
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched()
fmt.Println(s)
}
}
func main() {
go say("world") //开一个新的Goroutines执行
say("hello") //当前Goroutines执行
}
输出:
hello
world
hello
world
hello
world
hello
world
hello
我们可以看到go关键字很方便的就实现了并发编程。
##channels
Goroutines运行在相同的地址空间因此访问共享内存必须做好同步。那么Goroutines之间如何进行数据的通信呢Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比可以通过它发送或者接收值。这些值只能是特定的类型channel类型。定义一个channel 时也需要定义发送到channel 的值的类型。注意必须使用make 创建channel
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
channel通过操作符`<-`来接收和发送数据
ch <- v // 发送v到channel ch.
v := <-ch // 从ch中接收数据并赋值给v
我们把这些应用到我们的例子中来:
package main
import "fmt"
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x + y)
}
默认情况下channel接收和发送数据都是阻塞的除非另一端已经准备好这样就使得Goroutines同步变的更加的简单而不需要显式的lock。所谓阻塞也就是如果读取value := <-ch它将会被阻塞直到有数据接收。其次任何发送ch<-5将会被阻塞直到数据被读出。无缓冲channel 是在多个goroutine之间同步很棒的工具。
##Buffered Channels
上面我们介绍了默认的非缓存类型的channel不过Go也允许指定channel的缓冲大小很简单就是channel可以存储多少元素。ch:= make(chan bool, 4)创建了可以存储4个元素的bool 型channel。在这个channel 中前4个元素可以无阻塞的写入。当写入第5个元素时代码将会阻塞直到其他goroutine从channel 中读取一些元素,腾出空间。
ch := make(chan type, value)
value == 0 ! 无缓冲(阻塞)
value > 0 ! 缓冲非阻塞直到value 个元素)
我们看一下下面这个例子你可以在自己本机测试一下修改相应的value值
package main
import "fmt"
func main() {
c := make(chan int, 2)//修改2为1就报错修改2为3可以正常运行
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
##Range和Close
上面这个例子中我们需要读取两次c这样不是很方便Go考虑到了这一点所以也可以通过range像操作slice或者map一样操作缓存类型的channel请看下面的例子
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 1, 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)
}
}
`for i := range c`能够不断的读取channel里面的数据直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel生产者通过关键字`close`函数关闭channel。关闭channel之后就无法再发送任何数据了在消费方可以通过语法`v, ok := <-ch`测试channel是否被关闭。如果ok返回false那么说明channel已经没有任何数据并且已经被关闭。
>记住应该在生产者的地方关闭channel而不是消费的地方去关闭它这样容易引起panic
>另外记住一点的就是channel不像文件之类的不需要经常去关闭只有当你确实没有任何发送数据了或者你想显式的结束range循环之类的
##Select
我们上面介绍的都是只有一个channel的情况那么如果存在多个channel的时候我们该如何操作呢Go里面提供了一个关键字`select`,通过`select`可以监听channel上的数据流动。
`select`默认是阻塞的只有当监听的channel中有发送或接收可以进行时才会运行当多个channel都准备好的时候select是随机的选择一个执行的。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 1, 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)
}
虽然goroutine是并发执行的但是它们并不是并行运行的。如果不告诉Go额外的东西同一时刻只会有一个goroutine执行逻辑代码。利用runtime.GOMAXPROCS(n)可以设置goroutine并行执行的数量。GOMAXPROCS 设置了同时运行逻辑代码的系统线程 的最大数量并返回之前的设置。如果n < 1不会改变当前设置。以后Go的新版本中调度得到改进后这将被移除。
`select`里面还有default语法`select`其实就是类似switch的功能default就是当监听的channel都没有准备好的时候默认执行的select不再阻塞等待channel
select {
case i := <-c:
// use i
default:
// 当c阻塞的时候执行这里
}
## links
* [目录](<preface.md>)
* 上一章: [interface](<2.6.md>)
* 下一节: [总结](<2.8.md>)
## LastModified
* $Id$