一、Go程
Go 程(goroutine)是由 Go 運行時管理的輕量級線程。
go f(x, y, z)
會啟動一個新的 Go 程并執(zhí)行
f(x, y, z)
f, x, y 和 z 的求值發(fā)生在當前的 Go 程中之众,而 f 的執(zhí)行發(fā)生在新的 Go 程中。
Go 程在相同的地址空間中運行,因此在訪問共享的內(nèi)存時必須進行同步所踊。sync
包提供了這種能力,不過在 Go 中并不經(jīng)常用到概荷,因為還有其它的辦法(見下一頁)秕岛。
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
----------------------------------------------------------------------------
//運行結(jié)果:
hello
world
hello
world
hello
world
hello
world
hello
二、信道
(1)信道
信道是帶有類型的管道误证,你可以通過它用信道操作符 <- 來發(fā)送或者接收值继薛。
ch <- v // 將 v 發(fā)送至信道 ch。
v := <-ch // 從 ch 接收值并賦予 v愈捅。
(“箭頭”就是數(shù)據(jù)流的方向遏考。)
和映射與切片一樣,信道在使用前必須創(chuàng)建:
ch := make(chan int)
默認情況下蓝谨,發(fā)送和接收操作在另一端準備好之前都會阻塞灌具。這使得 Go 程可以在沒有顯式的鎖或競態(tài)變量的情況下進行同步青团。
以下示例對切片中的數(shù)進行求和,將任務(wù)分配給兩個 Go 程咖楣。一旦兩個 Go 程完成了它們的計算督笆,它就能算出最終的結(jié)果。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 將和送入 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 // 從 c 中接收
fmt.Println(x, y, x+y)
}
//運行結(jié)果:-5 17 12
btw诱贿,Go程序并不會去保證這些goroutine會以怎樣的順序運行
所以哪個goroutine先執(zhí)行完娃肿,哪個goroutine后執(zhí)行完往往是不可預(yù)知的,除非我們使用了某種Go語言提供的方式進行人為干預(yù)珠十。
(2)帶緩沖的信道
信道可以是 帶緩沖的咸作。將緩沖長度作為第二個參數(shù)提供給 make 來初始化一個帶緩沖的信道:
ch := make(chan int, 100)
僅當信道的緩沖區(qū)填滿后,向其發(fā)送數(shù)據(jù)時才會阻塞宵睦。當緩沖區(qū)為空時记罚,接受方會阻塞。
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
//運行結(jié)果:1 2
三壳嚎、range和close
發(fā)送者可通過 close 關(guān)閉一個信道來表示沒有需要發(fā)送的值了桐智。接收者可以通過為接收表達式分配第二個參數(shù)來測試信道是否被關(guān)閉:若沒有值可以接收且信道已被關(guān)閉,那么在執(zhí)行完
v, ok := <-ch
此時 ok 會被設(shè)置為 false烟馅。
循環(huán) for i := range c 會不斷從信道接收值说庭,直到它被關(guān)閉。
注意: 只有發(fā)送者才能關(guān)閉信道郑趁,而接收者不能刊驴。向一個已經(jīng)關(guān)閉的信道發(fā)送數(shù)據(jù)會引發(fā)程序恐慌(panic)。
還要注意: 信道與文件不同寡润,通常情況下無需關(guān)閉它們捆憎。只有在必須告訴接收者不再有需要發(fā)送的值時才有必要關(guān)閉,例如終止一個 range 循環(huán)梭纹。
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)
}
}
// 運行結(jié)果:
0
1
1
2
3
5
8
13
21
34
四躲惰、select語句
select 語句使一個 Go 程可以等待多個通信操作。
select 會阻塞到某個分支可以繼續(xù)執(zhí)行為止变抽,這時就會執(zhí)行該分支础拨。當多個分支都準備好時會隨機選擇一個執(zhí)行。
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)
}
//運行結(jié)果:
0
1
1
2
3
5
8
13
21
34
quit
五绍载、默認選擇
當 select 中的其它分支都沒有準備好時诡宗,default 分支就會執(zhí)行。
為了在嘗試發(fā)送或者接收時不發(fā)生阻塞击儡,可使用 default 分支:
select {
case i := <-c:
// 使用 i
default:
// 從 c 中接收會阻塞時執(zhí)行
}
舉個例子:
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)
}
}
}
// 運行結(jié)果:
.
.
tick.
.
.
tick.
.
.
tick.
.
.
tick.
.
.
BOOM!
六塔沃、練習:等價二叉樹
不同二叉樹的葉節(jié)點上可以保存相同的值序列。例如曙痘,以下兩個二叉樹都保存了序列 1芳悲,1立肘,2边坤,3名扛,5,8茧痒,13
肮韧。
在大多數(shù)語言中,檢查兩個二叉樹是否保存了相同序列的函數(shù)都相當復(fù)雜旺订。 我們將使用 Go 的并發(fā)和信道來編寫一個簡單的解法弄企。
本例使用了 tree 包,它定義了類型:
type Tree struct {
Left *Tree
Value int
Right *Tree
}
- 實現(xiàn) Walk 函數(shù)区拳。
- 測試 Walk 函數(shù)拘领。
函數(shù) tree.New(k) 用于構(gòu)造一個隨機結(jié)構(gòu)的已排序二叉查找樹,它保存了值 k, 2k, 3k, ..., 10k樱调。
創(chuàng)建一個新的信道 ch 并且對其進行步進:
go Walk(tree.New(1), ch)
然后從信道中讀取并打印 10 個值约素。應(yīng)當是數(shù)字 1, 2, 3, ..., 10。
- 用 Walk 實現(xiàn) Same 函數(shù)來檢測 t1 和 t2 是否存儲了相同的值笆凌。
- 測試 Same 函數(shù)圣猎。
Same(tree.New(1), tree.New(1)) 應(yīng)當返回 true,而 Same(tree.New(1), tree.New(2)) 應(yīng)當返回 false乞而。
Tree
的文檔可在這里找到
package main
import (
"fmt"
"golang.org/x/tour/tree"
)
// Walk 步進 tree t 將所有的值從 tree 發(fā)送到 channel ch送悔。
func Walk(t *tree.Tree, ch chan int) {
if t.Left != nil {
Walk(t.Left, ch)
}
ch <- t.Value
if t.Right != nil {
Walk(t.Right, ch)
}
return
//close(ch)
}
// Same 檢測樹 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
ch1, ch2 := make(chan int), make(chan int)
go Walk(t1, ch1)
go Walk(t2, ch2)
if <-ch1 == <-ch2 {
return true
} else {
return false
}
}
func main() {
ch := make(chan int)
t1 := tree.New(1)
t2 := tree.New(2)
//從信道中打印10個值
go Walk(t1, ch)
for i := 0; i < 10; i++ {
fmt.Println(<-ch)
}
//對tree1和tree2進行比較
fmt.Println("tree 1 == tree 1:", Same(t1, t1))
fmt.Println("tree 1 == tree 2:", Same(t1, t2))
}
-------------------------------------------------------------------------------------
// 運行結(jié)果:
tree 1 == tree 1: true
tree 1 == tree 2: false
七爪模、sync.Mutex
八欠啤、練習:Web爬蟲
插曲:
由于等價二叉樹程序代碼的實現(xiàn) 需要用到"golang.org/x/tour/tree"
這個包,而golang的官網(wǎng)需要翻墻才能進去屋灌。故后來采用的是在github找到相關(guān)來源的包跪妥,然后在linux環(huán)境下進行安裝下載。(方法一: 這是稍微老一點的方法声滥,新版本添加了mod的功能眉撵,可以不用設(shè)置GOPATH直接安裝,這將在方法二中記錄)
方法一:
使用命令為:
go get -x -v github.com/golang/sys
//輸入github賬號用戶名和密碼
- btw落塑,命令 go get 可以根據(jù)要求和實際情況從互聯(lián)網(wǎng)上下載或更新指定的代碼包及其依賴包纽疟,并對它們進行編譯和安裝。
在上面這個示例中憾赁,我們從代碼托管站點Github上下載了一個項目(或稱代碼包)污朽,并安裝到了環(huán)境變量GOPATH中包含的第一個工作區(qū)中。
使用 go get 安裝命令龙考,系統(tǒng)會將下載好的安裝包自動放到環(huán)境變量GOPATH值中的第一個目錄路徑蟆肆。
實際上矾睦,go get命令所做的動作也被叫做代碼包遠程導(dǎo)入,而傳遞給該命令的作為代碼包導(dǎo)入路徑的那個參數(shù)又被叫做代碼包遠程導(dǎo)入路徑炎功。
more about go get
方法二:
明天學(xué)習內(nèi)容:熟悉go的基本命令和mod功能 以及第二種安裝方法 以及go語言小結(jié)
btw枚冗,CRT中文亂碼問題還沒解決(編碼已經(jīng)改至 UTF-8,在vim編輯中扔有亂碼現(xiàn)象)
可能的解決方法一
go的標準命令詳解
build install run install get mod