1. 程式人生 > 其它 >8.14GO之條件語句

8.14GO之條件語句

8.14GO之條件語句

Go語言條件語句

一些大題的和類C語言無異,但是:

  • Go 沒有三目運算子,所以不支援 ?: 形式的條件判斷。

相當於Java中的:

  public boolean isEmpty(){
return size == 0 ? true : false;
}

Go之條件語句

語句描述
[if 語句] if 語句 由一個布林表示式後緊跟一個或多個語句組成。
[if...else 語句] if 語句 後可以使用可選的 else 語句, else 語句中的表示式在布林表示式為 false 時執行。
[if 巢狀語句] 你可以在 ifelse if
語句中嵌入一個或多個 ifelse if 語句。
[switch 語句] switch 語句用於基於不同條件執行不同動作。
[select 語句] select 語句類似於 switch 語句,但是select會隨機執行一個可執行的case。如果沒有case可執行,它將阻塞,直到有case可執行。
Go條件語句之select語句

簡單理解:

  • chan關鍵字定義了goroutine中的管道通訊,一個goroutine可以和另一個goroutine進行通訊。

什麼是goroutine

goroutine是Go中最基本的執行單元。

特點:

  • 每一個Go程式至少有一個Goroutine:主Goroutine。

執行緒(Thread)

特點:

  • 輕量級程序(Lightweight Process,LWP)

  • 程式執行流的最小單元

一個標準執行緒的組成:

  • 執行緒ID

  • 當前指令指標(PC)

  • 暫存器集合

一個執行緒的特點:

  • 是程序中的一個實體,是被系統獨立排程和分派的基本單位

  • 多執行緒之間 共享堆,不共享棧。 執行緒切換由作業系統排程

協程(coroutine)

特點:

  • 稱微執行緒與子例程(或者稱為函式)

  • 是一種程式元件。相對子例程而言,協程更為一般和靈活,但在實踐中使用沒有子例程那樣廣泛

一個協程的特點:

  • 多協程之間 共享堆,不共享棧

執行緒與協程的不同點
  • 執行緒切換由作業系統排程

  • 協程的切換由程式設計師在程式碼中顯示控制

好處:

  • 避免了上下文切換的額外耗費

  • 兼顧了多執行緒的優點

  • 簡化了高併發程式的複雜

Goroutine--->需要深入瞭解、實踐。目前還不是很明白。

特點:

  • 協程不是併發的,而Goroutine支援併發的。

  • Goroutine可以理解為一種Go語言的協程。

  • 同時它可以執行在一個或多個執行緒上。-

  • goroutine支援協程之間的互相通訊--->使用關鍵字chanchannel的簡寫

channel的特點

特點:

  • 一個 channels 是一個通訊機制,可以讓一個 goroutine 通過它給另一個 goroutine 傳送值資訊。

  • 每個 channel 都有一個特殊的型別,也就是 channels 可傳送資料的型別。一個可以傳送 int 型別資料的 channel 一般寫為 chan int。

和多執行緒ThreadLocal不一樣的地方:

ThreadLocal:

  • ThreadLocal能夠放一個執行緒級別的變數

  • 本身能夠被多個執行緒共享使用,又能達到執行緒安全的目的

  • 在多執行緒環境下保證成員變數的安全

ThreadLocal相當於一個共享的記憶體區域,供多個執行緒共享堆的資料。執行緒只能共享堆的資料,不能共享棧的資料。

Goroutine:

  • Go語言提倡使用通訊的方法代替共享記憶體

  • 資源需要在 goroutine 之間共享時,通道在 goroutine 之間架起了一個管道,並提供了確保同步交換資料的機制。

  • 因為go是強型別語言,所以宣告通道時,需要指定將要被共享的資料的型別。

  • 可以通過通道共享goroutine當中的內建型別、命名型別、結構型別和引用型別的值或者指標。

可以說goroutine實現了協程之間的棧的資料的共享。

通訊的方法就是使用通道(channel),如下圖所示:

channel 是一種佇列一樣的結構。

channel規則的特點:

  • 可以向一個goroutine建立多個channel通道

  • 同時只能有一個 goroutine 訪問通道進行傳送和獲取資料。

  • goroutine遵循先入先出(First In First Out)的規則,保證收發資料的順序。--->和棧空間的規則一樣

通道的使用方式
  • 宣告通道

  • 建立通道

  • 使用通道傳送資料


通道本身需要一個型別進行修飾,就像切片型別需要標識元素型別。通道的元素型別就是在其內部傳輸的資料型別

宣告通道型別格式:
var 通道變數 chan 通道型別
  • 通道型別:通道內的資料型別。

  • 通道變數:儲存通道的變數。--->通道本身也是一個變數,

chan 型別的空值是 nil,聲明後需要配合 make 後才能使用。

建立通道格式--->通道本身是引用型別,需要使用make進行建立
通道例項 := make(chan 資料型別)
  • 資料型別:通道內傳輸的元素型別。

  • 通道例項:通過make建立的通道控制代碼。--->可以理解為在堆中開闢一個空間

示例:

package main

import (
"expvar"
"fmt"
)

func main() {
ch1 := make(chan int) //建立一個整數型別的通道
ch2 := make(chan interface{}) //建立一個空介面型別的通道, 可以存放任意格式

type Equip struct {
expvar.Var
}

ch2 := make(chan *Equip)
fmt.Println()
}
使用通道傳送資料--->操作符:<-

通道傳送蘇劇的格式:

通道變數 <- 
  • 通道變數:通過make建立好的通道例項。--->這是一個堆裡面的實際物件,對應到一個堆的地址

  • 值:可以是變數、常量、表示式或者函式返回值等。值的型別必須與ch通道的元素型別一致。

示例:

package main

func main() {
ch1 := make(chan int) //建立一個整數型別的通道
ch2 := make(chan interface{}) //建立一個空介面型別的通道, 可以存放任意格式

ch1 <- 1
ch2 <- 3.1415926
ch2 <- "Hello,World!"
}
使用通道接受資料--->操作符:<-

通道接收資料的特徵:

  • 通道的收發操作在不同的兩個 goroutine 間進行。

    • 通道的資料在沒有接收方處理時,資料傳送方會持續阻塞,因此通道的接收必定在另外一個 goroutine 中進行。

  • 接收將持續阻塞直到傳送方傳送資料。

    • 接收方接收時,通道中沒有傳送方傳送資料,接收方也會發生阻塞,直到傳送方傳送資料為止。

  • 每次接收一個元素。

    • 通道一次只能接收一個數據元素。

概括:

  • 通過通道傳送資料需要兩個goroutine

  • 傳送沒有接收會阻塞goroutine,接收沒有傳送資料也會阻塞goroutine--->非常類似Java多執行緒中的執行緒通訊模型

  • 可以建立非阻塞接收資料的型別

接收資料的四種寫法:

  1. 阻塞式接收資料
  2. 非阻塞式接收資料
  3. 接收任意資料,忽略接收的資料
  4. 迴圈接收

阻塞式接收資料:

data <- ch

執行該語句時將會阻塞,直到接收到資料並賦值給 data 變數。

非阻塞式接收資料:

data, ok <- ch
  • data:表示接收到的資料。未接收到資料時,data 為通道型別的零值。

  • ok:表示是否接收到資料。

非阻塞的通道接收方法可能造成高的 CPU 佔用,因此使用非常少。如果需要實現接收超時檢測,可以配合 select 和計時器 channel 進行

接收任意資料,忽略接收的資料:

<- ch

阻塞接收資料後,忽略從通道返回的資料。執行該語句時將會發生阻塞,直到接收到資料,但接收到的資料會被忽略。

通過通道在 goroutine 間阻塞收發實現併發同步。

使用通道做同步併發的例項:

package main

import "fmt"

func main() {
//構建一個通道
ch := make(chan int)

//開啟一個併發匿名函式
go func() {
fmt.Println("start goroutine")

//通過通道通知main的goroutine
ch <- 0
fmt.Println("exit goroutine")
}()

fmt.Println("wait goroutine")

//等待匿名goroutine
<- ch

fmt.Println("all done")
}

可以看到執行順序:

  1. 先執行main中的構建和通道接收的內容。到<- ch此時goroutine進入阻塞狀態

  2. 接著等到go func()當中的goroutine執行向通道ch中新增值

  3. 最後main中收到了值以後解除阻塞狀態,往下執行

所以輸出順序是:

wait goroutine
start goroutine
exit goroutine
all done

程式碼說明:

ch := make(chan int) //構建一個同步用的通道。
go func() //開啟一個匿名函式的併發。
ch <- 0 //匿名 goroutine 即將結束時,通過通道通知 main 的 goroutine,這一句會一直阻塞直到 main 的 goroutine 接收為止。
<- ch //開啟 goroutine 後,馬上通過通道等待匿名 goroutine 結束。

雖然是併發執行但是通過阻塞的方式還是可以實現資料的安全、準確性

迴圈接收:

借用 for range 語句進行多個元素的接收操作

for data := range ch {
}

特點:

  • 通道 ch 可以進行遍歷--->遍歷的結果就是接收到的資料。資料型別就是通道的資料型別。

  • 通過 for 遍歷獲得的變數只有一個,即 data。

有點類似增強for迴圈

示例:

package main

import (
"fmt"
"time"
)

func main() {
//構建一個通道
chNo2 := make(chan int)

//開啟一個併發匿名函式
go func() {

//for迴圈,從3->0
for i := 3; i > 0; i-- {
//每次將迴圈的值傳送到main的goroutine中
chNo2 <- i

//每次傳送完時等待
time.Sleep(time.Second)
}
}()

//遍歷接收通道的資料
for data := range chNo2 {
//列印通道資料
fmt.Println(data)

//當遇到資料0時,退出接收迴圈
if data == 0 {
break
}
}
}

程式碼說明:

ch := make(chan int)
//通過 make 生成一個整型元素的通道。
go func() //將匿名函式併發執行。
ch <- i //將 3 到 0 之間的數值依次傳送到通道 ch 中。
for data := range ch //使用 for 從通道中接收資料。

本篇內容參考:

Go語言通道(chan)——goroutine之間通訊的管道

Go goroutine理解 - SegmentFault 思否

go的併發實現原理在後續學習到以後會深入的記錄學習過程。

It's a lonely road!!!