一日一學_Go從錯誤中學習基礎二

上一篇(一日一學_Go從錯誤中學習基礎一)講了部分Golang容易出錯地方,為了讓讀者清晰學習,我決定分開。

new()與make()使用

數組、結構體和所有的值類型都可以使用new,切片、映射和通道,使用make。


多么簡單的概念

為了能更深刻的理解,不混淆使用new和make下面開展內存的分析,防止跳坑

type Person struct {
    name string
    age  int
}

func main() {
    p1 := Person{"wuxiao", 10}
    p2 := &PersonP{"wuxiao", 15} // == new(Person)
}

p1內存狀態圖

可以看出p1在內存中是以連續的內存塊存在

p2內存狀態圖

十六進制值表示指針地址0x ..,并引用實際數據。
在一些博客中,經常提到如下例子:

p1 := Person{"wuxiao", 10}
    
func setName(p Person) {
    p.name = "xiaobai"
}

上面code進行了值拷貝

值拷貝
p2 := &PersonP{"wuxiao", 15}
    
func setName(p *Person) {
    p.name = "dabai"
}

指針拷貝

從上面分析看出,第一種情況在函數setName() 中修改p不會修改原始數據,但是在第二種情況下肯定會修改,因為存儲在p中的地址引用了原始數據塊。
總結: 將一個值類型作為一個參數傳遞給函數或者作為一個方法的接收者,似乎是對內存的濫用,因為值類型一直是傳遞拷貝。但是另一方面,值類型的內存是在棧上分配,內存分配快速且開銷不大。因此當使用指針代替值類型作為參數傳遞時,需要根據自己需求來使用。
接下來我以切片理解make。
我們創建一個具有6個元素的底層數組的切片,使用make([] int,len,cap)語法來指定容量。如下:

    arr = make([]int, 5, 6)
    arr[3] = 44
    arr[4] = 333
切片內存狀態

我們創建另一個子切片并更改一些元素,會發生什么?

        arr := make([]int, 5, 6)
    arr[3] = 44
    arr[4] = 333
    subArr := arr[1:4]
    subArr[1] = 77
切片

修改了subArr同樣修改了底層數組,這就是為什么在Golang中切片使用廣泛的原因。

在使用內置函數append()對切片進行添加元素時,但在內部它做了很多復雜的工作,來進行內存分配。

   arr := make([]int, 5, 6)
   subArr := arr[:]
   arr = append(arr, 1, 2)

使用append() 時會檢查該切片是否有未使用的容器個數,如果沒有,則分配更多的內存。 分配內存是一個相當昂貴的操作,因此append嘗試對該操作進行預估,一次增加原始容量的兩倍。 一次分配較多的內存通常比多次分配較少的內存更高效和更快。
分配更多的內存通常意味著分配新內存并從舊數組拷貝數據到新數組(導致地址值的改變)。

append后的內存變化

可以看出會有兩個不同的底層數組,這對初學者來說可能不經意中出錯。

協程與通道使用

協程與通道我認為是Golang的核心,為了能大家通俗易懂了解,防止在編寫代碼出錯,我接下來會以一些例子來講解核心部分。

看這里重點

無緩沖與有緩沖channel有什么區別?
一個是同步。
一個是非同步。
簡單點理解:
無緩沖 使用通道發送數據,必須有此通道類型的協程接收數據,才能繼續發送數據,要不然進入永久阻塞(死鎖)。

有緩沖 如果緩沖大小是1,在通道發送數據時,只有當放第二個值的時候,第一個還沒被人拿走,這時候才會阻塞。

下面三個例子更好說明了同步與非同步:

    data := make(chan string)

    //因為data沒有值,所以會選擇default執行(發送接收數據都
           //是一樣的道理),這里就達成看一個無阻塞的效果。
    select {
    case msg := <-data:
        fmt.Println("received data", msg)
    default:
        fmt.Println("no data.....")
    }

執行結果:no data.....

     data := make(chan string, 1) //給通道加上緩沖的話,會怎么選擇?
     data <- "wuxiao"

    //猜測:data已經有值,所以會選擇 received data 執行。
    select {
    case msg := <-data:
        fmt.Println("received data", msg)
    default:
        fmt.Println("no data.....")
    }

執行結果:received data wuxiao

  • 如上面所說,在緩沖未裝滿時,給一個帶緩沖的緩存發送數據是不會阻塞的,而從緩沖讀取數據也不會阻塞。
    data := make(chan string)//沒有緩沖了
    data <- "wuxiao" //因為該channels沒有緩沖,發送數據,導致死鎖(有的書上寫為永遠阻塞)
      
        select {
    case msg := <-data:
        fmt.Println("received data", msg)
    default:
        fmt.Println("no data.....")
    }

執行結果: fatal error: all goroutines are asleep - deadlock!
上面為什么會報錯?
官方解釋到: Unbuffered channels combine communication—the exchange of a value—with synchronization—guaranteeing that two calculations (goroutines) are in a known state
無緩沖的信道進行通信,保證兩個協程處于已知狀態。

流水線模式
開發中我們可以使用協程與通道達到并發的效果,而且還可以根據自己需求使用并發設計模式來提高效率,并發設計模式有扇入和扇出,流水線等。
流水線模式可以簡單理解由多個階段組成的,相鄰的兩個階段由 channel 進行連接;每個階段是都有自己goroutine 。每個階段都會執行下面三個操作(除了第一個和最后一個階段):

1. 通過 channel 接收數據上流的數據
2. 對接收到的數據進行操作
3. 將新生成的數據通過 channels 發送數據給下游

顯然,第一個階段只有發送管道,而最后一個階段只有接收管道.
通常稱第一個階段可以理解為"生產者",稱最后一個階段理解為"消費者"。



//第一階段是為gen 函數,首先啟動一個 goroutine,
//通過goroutine 把數字發送到 channel,當數字發送完時關閉channel。
func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}
//第二階段是 sq 函數,它從第一階段返回的通道來接受整數,把
//所接收的整數發送自己創建的通道中并返回給下游,并且等第一
//階段通道數字全部發給下游會關閉了上流通道,然后在關閉自己創建的管道。
func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

//main 函數為流水線的最后一個階段。
//會從第二階段接收數字,并逐個打印出來,直到來自于上游的接收管道關閉
func main() {
        //由于 sq 函數的channel 類型一樣,所以組合任意個 sq 函數
    for n := range sq(sq(gen(2, 4, 5)) ){
        fmt.Println(n)
    }
}

如果我把上面最后一個階段改成

    out := range sq(sq(gen(2, 4, 5)) )
    fmt.Println(<-out) // 16 or 256 , 625

這里存在資源泄漏。一方面goroutine 消耗內存和運行時資源,另一方面goroutine 棧中的堆引用會阻止 gc 執行回收操作。 既然goroutine 不能被回收,那么他們必須自己退出。
那么如何解決這個問題?
使用顯式取消。
在Go語言中,我們可以通過關閉一個channel 實現,因為在一個已關閉 channel 上執行接收操作數據總是能夠立即返回,返回值是對應類型的零值。
簡單講,對一個管道的關閉操作事實上是對所有接收者進行廣播信號。

func main() {
    // 當關閉 done channel時
    //給所有 goroutine發送信號
    // 接收到后都會正常退出。
    done := make(chan struct{})
    defer close(done)

    in := gen(done, 2, 4, 5)
    c1 := sq(done, in)
    out := sq(done, c1)
    fmt.Println(<-out) 
}
func sq(done <-chan struct{}, in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            select {
            case out <- n * n:
            case <-done:
                return
            }
        }
    }()
    return out
} 

其實講上面的模式就是為了,提醒我們使用協程和通道的時候小心資源泄漏,
發送完數據以后進行close關閉,以防死鎖。
其他一些模式感興趣可以點擊查看模式學習地址一模式學習地址二

并發這塊我還很多不足,希望大家能多多討論.共同進步

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容

  • 出處---Go編程語言 歡迎來到 Go 編程語言指南。本指南涵蓋了該語言的大部分重要特性 Go 語言的交互式簡介,...
    Tuberose閱讀 18,477評論 1 46
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,338評論 11 349
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • 聊起高中時候的我們,你以一個年少無知掩過了你的那些事兒,而那時候的我不知道找了多少個理由去說服自己!
    living0519閱讀 271評論 0 1
  • 曾經看到過一段話,深深擊中我的內心:“如果你的孩子小時候特別聽話懂事,也許你要存好以后給孩子看心理醫生的錢。” 我...
    巫婆的口袋閱讀 1,234評論 0 51