中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang中的并發性是什么

發布時間:2023-03-15 14:07:40 來源:億速云 閱讀:104 作者:iii 欄目:開發技術

這篇文章主要介紹了Golang中的并發性是什么的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Golang中的并發性是什么文章都會有所收獲,下面我們一起來看看吧。

    什么是并發性,為什么它很重要

    并發是指在同一時間運行多個事物的能力。你的電腦有一個CPU。一個CPU有幾個線程。每個線程通常一次運行一個程序。當我們通常寫代碼時,這些代碼是按順序運行的,也就是說,每項工作都是背對背運行的。在并發代碼中,這些工作是由線程同時運行的。

    一個很好的比喻是對一個家庭廚師的比喻。我還記得我第一次嘗試煮意大利面的時候。我按照菜譜一步步地做。我切了蔬菜,做了醬汁,然后煮了意大利面條,再把兩者混合起來。在這里,每一步都是按順序進行的,所以下一項工作必須等到當前工作完成后才能進行。

    快進到現在,我在烹飪意大利面條方面變得更有經驗。我現在先開始做意大利面,然后在這期間進行醬汁的制作。烹飪時間幾乎減少到一半,因為烹飪意大利面條和醬汁是同時進行的。

    并發性與平行性

    并發性與并行性有些不同。并行性與并發性類似,即同時發生多項工作。然而,在并行性中,多個線程分別在進行不同的工作,而在并發性中,一個線程在不同的工作之間游走。

    因此,并發性和并行性是兩個不同的概念。一個程序既可以并發地運行,也可以并行地運行。你的代碼可以按順序寫,也可以按并發寫。該代碼可以在單核機器或多核機器上運行。把并發性看作是你的代碼的一個特征,而把并行性看作是執行的一個特征。

    Goroutines, the worker Mortys

    Go使編寫并發代碼變得非常簡單。每個并發的工作都由一個goroutine來表示。你可以通過在函數調用前使用go關鍵字來啟動一個goroutine。看過《瑞克和莫蒂》嗎?想象一下,你的主函數是一個Rick,他把任務委托給goroutine Mortys。

    讓我們從一個連續的代碼開始。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        simple()
    }
    
    func simple() {
        fmt.Println(time.Now(), "0")
        time.Sleep(time.Second)
    
        fmt.Println(time.Now(), "1")
        time.Sleep(time.Second)
    
        fmt.Println(time.Now(), "2")
        time.Sleep(time.Second)
    
        fmt.Println("done")
    }

    2022-08-14 16:22:46.782569233 +0900 KST m=+0.000033220 0
    2022-08-14 16:22:47.782728963 +0900 KST m=+1.000193014 1
    2022-08-14 16:22:48.782996361 +0900 KST m=+2.000460404 2
    done

    上面的代碼打印出當前時間和一個字符串。每條打印語句的運行時間為一秒。總的來說,這段代碼大約需要三秒鐘的時間來完成。

    現在讓我們把它與一個并發的代碼進行比較。

    func main() {
        simpleConc()
    }
    
    func simpleConc() {
        for i := 0; i < 3; i++ {
            go func(index int) {
                fmt.Println(time.Now(), index)
            }(i)
        }
    
        time.Sleep(time.Second)
        fmt.Println("done")
    }

    2022-08-14 16:25:14.379416226 +0900 KST m=+0.000049175 2
    2022-08-14 16:25:14.379446063 +0900 KST m=+0.000079012 0
    2022-08-14 16:25:14.379450313 +0900 KST m=+0.000083272 1
    done

    上面的代碼啟動了三個goroutines,分別打印當前時間和i。這段代碼花了大約一秒鐘完成。這比順序版本快了三倍左右。

    "等一下,"我聽到你問。"為什么要等整整一秒?難道我們不能刪除這一行以使程序盡可能快地運行嗎?"好問題!讓我們看看會發生什么。

    func main() {
        simpleConcFail()
    }
    
    func simpleConcFail() {
        for i := 0; i < 3; i++ {
            go func(index int) {
                fmt.Println(time.Now(), index)
            }(i)
        }
    
        fmt.Println("done")
    }

    done

    嗯......。程序確實在沒有任何慌亂的情況下退出了,但我們缺少來自goroutines的輸出。為什么它們被跳過?

    這是因為在默認情況下,Go并不等待goroutine的完成。你知道main也是在goroutine里面運行的嗎?主程序通過調用simpleConcFail來啟動工作程序,但它在工作程序完成工作之前就退出了。

    讓我們回到烹飪的比喻上。想象一下,你有三個廚師,他們分別負責烹飪醬料、意大利面和肉丸。現在,想象一下,如果戈登-拉姆齊命令廚師們做一盤意大利面條和肉丸子。這三位廚師將努力工作,烹制醬汁、意大利面條和肉丸。但是,在廚師們還沒有完成的時候,戈登就按了鈴,命令服務員上菜。很明顯,食物還沒有準備好,顧客只能得到一個空盤子。

    這就是為什么我們在退出節目前等待一秒鐘。我們并不總是確定每項工作都會在一秒鐘內完成。有一個更好的方法來等待工作的完成,但我們首先需要學習另一個概念。

    總結一下,我們學到了這些東西:

    • 工作被委托給goroutines。

    • 使用并發性可以提高你的性能。

    • 主goroutine默認不等待工作goroutine完成。

    • 我們需要一種方法來等待每個goroutine完成。

    Channels, the green portal

    goroutines之間是如何交流的?當然是通過通道。通道的作用類似于門戶。你可以通過通道發送和接收數據。下面是你如何在Go中制作一個通道。

    ch := make(chan int)

    每個通道都是強類型的,并且只允許該類型的數據通過。讓我們看看我們如何使用這個。

    func main() {
        unbufferedCh()
    }
    
    func unbufferedCh() {
        ch := make(chan int)
    
        go func() {
            ch <- 1
        }()
    
        res := <-ch
        fmt.Println(res)
    }

    1

    很簡單,對嗎?我們做了一個名為ch的通道。我們有一個goroutine,向ch發送1,我們接收該數據并將其保存到res。

    你問,為什么我們在這里需要一個goroutine?因為不這樣做會導致死鎖。

    func main() {
        unbufferedChFail()
    }
    
    func unbufferedChFail() {
        ch := make(chan int)
        ch <- 1
        res := <-ch
        fmt.Println(res)
    }

    fatal error: all goroutines are asleep - deadlock!

    我們碰到了一個新詞。什么是死鎖?死鎖就是你的程序被卡住了。為什么上面的代碼會卡在死鎖中?

    為了理解這一點,我們需要知道通道的一個重要特性。我們創建了一個無緩沖的通道,這意味著在某一特定時間內沒有任何東西可以被存儲在其中。這意味著發送方和接收方都必須同時準備好,才能在通道上傳輸數據。

    在失敗的例子中,發送和接收的動作依次發生。我們發送1到ch,但在那個時候沒有人接收數據。接收發生在稍后的一行,這意味著在接收行運行之前,1不能被發送。可悲的是,1不能先被發送,因為ch是沒有緩沖的,沒有空間來容納任何數據。

    在這個工作例子中,發送和接收的動作同時發生。主函數啟動了goroutine,并試圖從ch中接收,此時goroutine正在向ch發送1。

    另一種從通道接收而不發生死鎖的方法是先關閉通道。

    func main() {
        unbufferedCh()
    }
    
    func unbufferedCh() {
        ch3 := make(chan int)
        close(ch3)
        res2 := <-ch3
        fmt.Println(res2)
    }

    0

    關閉通道意味著不能再向它發送數據。我們仍然可以從該通道中接收它。對于未緩沖的通道,從一個關閉的通道接收將返回一個通道類型的零值。

    總結一下,我們學到了這些東西:

    • 通道是goroutines之間相互交流的方式。

    • 你可以通過通道發送和接收數據。

    • 通道是強類型的。

    • 沒有緩沖的通道沒有空間來存儲數據,所以發送和接收必須同時進行。否則,你的代碼就會陷入死鎖。

    • 一個封閉的通道將不接受任何數據。

    • 從一個封閉的非緩沖通道接收數據將返回一個零值。

    如果通道能保持數據一段時間,那不是很好嗎?這里就是緩沖通道發揮作用的地方。

    Buffered channels, the portal that is somehow cylindrical?

    緩沖通道是帶有緩沖器的通道。數據可以存儲在其中,所以發送和接收不需要同時進行。

    func main() {
        bufferedCh()
    }
    
    func bufferedCh() {
        ch := make(chan int, 1)
        ch <- 1
        res := <-ch
        fmt.Println(res)
    }

    1

    在這里,1被儲存在ch里面,直到我們收到它。

    很明顯,我們不能向一個滿了緩沖區的通道發送更多的信息。你需要在緩沖區內有空間才能發送更多。

    func main() {
        bufferedChFail()
    }
    
    func bufferedChFail() {
        ch := make(chan int, 1)
        ch <- 1
        ch <- 2
        res := <-ch
        fmt.Println(res)
    }

    fatal error: all goroutines are asleep - deadlock!

    你也不能從一個空的緩沖通道接收。

    func main() {
        bufferedChFail2()
    }
    
    func bufferedChFail2() {
        ch := make(chan int, 1)
        ch <- 1
        res := <-ch
        res2 := <-ch
        fmt.Println(res, res2)
    }

    fatal error: all goroutines are asleep - deadlock!

    如果一個通道已滿,發送操作將等待,直到有可用的空間。這在這段代碼中得到了證明。

    func main() {
        bufferedCh3()
    }
    
    func bufferedCh3() {
        ch := make(chan int, 1)
        ch <- 1
        go func() {
            ch <- 2
        }()
        res := <-ch
        fmt.Println(res)
    }

    1

    我們接收一次是為了取出1,這樣goroutine就可以發送2到通道。我們沒有從ch接收兩次,所以只接收1。

    我們也可以從封閉的緩沖通道接收。在這種情況下,我們可以在封閉的通道上設置范圍來迭代里面的剩余項目。

    func main() {
        bufferedChRange()
    }
    
    func bufferedChRange() {
        ch := make(chan int, 3)
        ch <- 1
        ch <- 2
        ch <- 3
        close(ch)
        for res := range ch {
            fmt.Println(res)
        }
        // you could also do this
        // fmt.Println(<-ch)
        // fmt.Println(<-ch)
        // fmt.Println(<-ch)
    }

    1
    2
    3

    在一個開放的通道上測距將永遠不會停止。這意味著在某些時候,通道將是空的,測距循環將試圖從一個空的通道接收,從而導致死鎖。

    總結一下:

    • 緩沖通道是有空間容納項目的通道。

    • 發送和接收不一定要同時進行,與非緩沖通道不同。

    • 向一個滿的通道發送和從一個空的通道接收將導致一個死鎖。

    • 你可以在一個封閉的通道上進行迭代,以接收緩沖區內的剩余值。

    等待戈多...我的意思是,goroutines來完成,使用通道

    通道可以用來同步goroutines。還記得我告訴過你,在通過無緩沖通道傳輸數據之前,發送方和接收方必須都準備好了嗎?這意味著接收方將等待,直到發送方準備好。我們可以說,接收是阻斷的,意思是接收方將阻斷其他代碼的運行,直到它收到東西。讓我們用這個巧妙的技巧來同步我們的goroutines。

    func main() {
        basicSyncing()
    }
    
    func basicSyncing() {
        done := make(chan struct{})
    
        go func() {
            for i := 0; i < 5; i++ {
                fmt.Printf("%s worker %d start\n", fmt.Sprint(time.Now()), i)
                time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
            }
            close(done)
        }()
    
        <-done
        fmt.Println("exiting...")
    }

    我們做了一個done通道,負責阻斷代碼,直到goroutine完成。done可以是任何類型,但struct{}經常被用于這些類型的通道。它的目的不是為了傳輸結構,所以它的類型并不重要。

    一旦工作完成,worker goroutine 將關閉 done。此時,我們可以從 done 中接收,它將是一個空結構。接收動作解除了代碼的阻塞,使其可以退出。

    這就是我們使用通道等待goroutine完成的方式。

    關于“Golang中的并發性是什么”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Golang中的并發性是什么”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    黎川县| 进贤县| 盐津县| 沁水县| 扶沟县| 大埔县| 夹江县| 寿阳县| 桃源县| 环江| 临武县| 闻喜县| 遵义市| 固安县| 新田县| 黑水县| 务川| 深州市| 大田县| 安远县| 桑植县| 治县。| 新余市| 霍邱县| 辉县市| 郎溪县| 阿荣旗| 秀山| 西昌市| 永州市| 汝阳县| 红安县| 和平区| 南投县| 府谷县| 万州区| 洞头县| 萨迦县| 汨罗市| 昌黎县| 本溪市|