前言
由于Go最近一系列出色的表现,从一开始Go便紧紧地吸引住了我的眼球。类似于Erlang、Scala等语言,Go也是天生为并发而设计的语言,Go有着许多在原生层面对并发编程进行支持的优秀特性,比如大名鼎鼎的Goroutines、Channels、Select等原生特性。那么废话不多说,这一篇主要是对GO语言中的并发编程模式做一个粗略的归纳总结,文中示例参考自golang conference中的一些演讲和博客,涉及到的Go语言的语法知识细节将予以略去。Go语言语法请参考http://golang.org/
几点强调之处
1. 并发而非并行
首先我们要明确两个名词:并发(Concurrency)、并行(Parallelism)。这两个词可能大家经常搞混淆,因为这两个词所标书的意思太过相近,但是前者更加偏向于设计(Design),而后者更加偏向于结构(Structure)。
如果你有只有一个CPU,那么你的程序可以是并发的,但一定不是并行的
一个良好的并发程序并非一定是并行的
并行是一种物理状态,而并发是一种设计思想、程序的内部结构
多处理器才有可能达到并发的物理状态
2. 什么是Goroutines
Goroutine是一个通过go关键字起起来的独立的执行某个function的过程,它拥有独立的可以自行管理的调用栈。
goroutine非常廉价,你可以拥有几千甚至上万的goroutines
goroutine不是thread
一个thread之下可能有上千的goroutines
你可以把goroutine理解为廉价的thread
让我们从几个例子开始
一个很无聊的函数
func boring(msg string) {for i := 0; ; i++ {fmt.Println(msg, i)time.Sleep(time.Second)}
}
显而易见,这个函数永不停歇的打印msg字符串,并且循环中间会sleep一秒钟,接下来让我们不断改进这个函数。
嗯哼,稍微不那么无聊一点了
func boring(msg string) {for i := 0; ; i++ {fmt.Println(msg, i)time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)}
}
我们看到这个函数不再是sleep固定的时间,而是rand出一个随机的duration。这样,可以让我们的这个无聊的函数稍微不可预期一点。
让我们把它run起来!~Let’s go!
func main() {boring("boring!")
}func boring(msg string) {for i := 0; ; i++ {fmt.Println(msg, i)time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)}
}
好了,无聊的函数跑起来了~~但是,目前我们还没有用到Goroutines的特性
让函数Go起来!
package mainimport ("fmt""math/rand""time"
)func main() {go boring("boring!")
}
程序的输出为:
[no output]Program exited.
纳尼??!!奇怪啊,为什么程序没有输出捏?其实真相是,main函数在开启boring方法的新的goroutine之后,没有等待boring方法调用fmt.Println就急急忙忙返回退出了。当main退出之时,我们的程序自然而然也就退出了。
让我们等一下TA吧
func main() {go boring("boring!")fmt.Println("I'm listening.")time.Sleep(2 * time.Second)fmt.Println("You're boring; I'm leaving.")
}
现在我们就可以在主程序退出之前看到boring函数输出的message了。但是等等,我们现在还只是一个goroutine,还没有涉及到真正意义上的并发。
使用channels!
让我们先来看一个简单的使用channels进行同步的例子
var syn chan int = make(chan int)func foo(){for(i := 0; i < 5; i++){fmt.Println("i am running!")}syn <- 1
}func main(){go foo()<- syn
}
很简单吧,通过使用通道syn,可以进行简单的同步。这样,在main函数退出之间首先会在读取syn处阻塞,除非foo向syn写入数据。
让boring和main成为好基友
func boring(msg string, c chan string) {for i := 0; ; i++ {c <- fmt.Sprintf("%s %d", msg, i)time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)}
}func main() {c := make(chan string)go boring("boring!", c)for i := 0; i < 5; i++ {fmt.Printf("You say: %q\n", <-c)}fmt.Println("You're boring; I'm leaving.")
}
我们通过channel将main和boring联系起来,从而让本来毫无关系的他们能够自然地交流,从而知晓彼此的状态。上面程序便是通过channel来进行的同步。当main函数执行 “<- c”时会发生阻塞,除非boring中执行”c <- fmt.Sprintf(“%s %d”, msg, i)”向通道中写入数据才会解除阻塞。由此观之,即针对同一个channel,sender和receiver必须要一个读一个写才能使得channel畅通不阻塞。如此一来,便可以通过channel进行交流和同步。
转载地址
https://studygolang.com/articles/420