【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進行交流和同步。

相關文章
相關標籤/搜索