【go语言】Goroutines 并发模式(一)

前言

因为前一阶段实习中接到的项目的告一段落,不知不觉便多出了许多空余的时间,因而就想总结一下最近由于我的兴趣而学习的一些东西。从这篇文章开始以及后面陆续的几篇关于GO语言的文章,均是博主最近对GO语言学习过程当中的一些感悟、总结,相似于学习笔记的东西。记录下来并整理成博客一为对学习的知识作一个整理,二为分享出来给你们(由于国内关于GO语言的中文资料比较少),因为博主能力和知识有限,不免有所靡误,还望勘正。
golang

因为Go最近一系列出色的表现,从一开始Go便牢牢地吸引住了个人眼球。相似于Erlang、Scala等语言,Go也是天生为并发而设计的语言,Go有着许多在原生层面对并发编程进行支持的优秀特性,好比大名鼎鼎的Goroutines、Channels、Select等原生特性。那么废话很少说,这一篇主要是对GO语言中的并发编程模式作一个粗略的概括总结,文中示例参考自golang conference中的一些演讲和博客,涉及到的Go语言的语法知识细节将予以略去。Go语言语法请参考http://golang.org/shell


几点强调之处

1. 并发而非并行

首先咱们要明确两个名词:并发(Concurrency)、并行(Parallelism)。这两个词可能你们常常搞混淆,由于这两个词所标书的意思太过相近,可是前者更加偏向于设计(Design),然后者更加偏向于结构(Structure)。编程

  • 若是你有只有一个CPU,那么你的程序能够是并发的,但必定不是并行的并发

  • 一个良好的并发程序并不是必定是并行的函数

  • 并行是一种物理状态,而并发是一种设计思想、程序的内部结构学习

  • 多处理器才有可能达到并发的物理状态spa

2. 什么是Goroutines

Goroutine是一个经过go关键字起起来的独立的执行某个function的过程,它拥有独立的能够自行管理的调用栈。

  • goroutine很是廉价,你能够拥有几千甚至上万的goroutines设计

  • goroutine不是threadcode

  • 一个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 main

import (
    "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进行交流和同步。

相关文章
相关标签/搜索