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

溫馨提示×

溫馨提示×

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

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

Golang并發編程怎么應用

發布時間:2023-05-08 15:21:15 來源:億速云 閱讀:85 作者:iii 欄目:開發技術

這篇文章主要講解了“Golang并發編程怎么應用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Golang并發編程怎么應用”吧!

    1、通過通信共享

    并發編程是一個很大的主題,這里只提供一些特定于go的重點內容。

    在許多環境中,實現對共享變量的正確訪問所需要的微妙之處使并發編程變得困難。Go鼓勵一種不同的方法,在這種方法中,共享值在通道中傳遞,實際上,從不由單獨的執行線程主動共享。在任何給定時間,只有一個goroutine可以訪問該值。根據設計,數據競爭是不可能發生的。為了鼓勵這種思維方式,我們把它簡化為一句口號:

    Do not communicate by sharing memory; instead, share memory by communicating.

    不要通過共享內存進行通信;相反,通過通信共享內存。

    這種方法可能走得太遠。例如,引用計數最好通過在整數變量周圍放置互斥來實現。但是作為一種高級方法,使用通道來控制訪問可以更容易地編寫清晰、正確的程序。

    考慮這個模型的一種方法是考慮一個典型的單線程程序運行在一個CPU上。它不需要同步原語。現在運行另一個這樣的實例;它也不需要同步。現在讓這兩個程序通信;如果通信是同步器,則仍然不需要其他同步。例如,Unix管道就完美地符合這個模型。盡管Go的并發方法起源于Hoare的通信順序處理(communication Sequential Processes, CSP),但它也可以被視為Unix管道的類型安全的泛化。

    2、Goroutines

    它們之所以被稱為goroutine,是因為現有的術語——線程、協程、進程等等——傳達了不準確的含義。goroutine有一個簡單的模型:它是一個與相同地址空間中的其他goroutine并發執行的函數。它是輕量級的,比分配棧空間的成本高不了多少。而且棧開始時很小,所以它們很便宜,并通過根據需要分配(和釋放)堆存儲來增長。

    goroutine被多路復用到多個操作系統線程上,因此如果一個線程阻塞,比如在等待I/O時,其他線程繼續運行。它們的設計隱藏了線程創建和管理的許多復雜性。

    在函數或方法調用前加上go關鍵字以在新的 goroutine 中運行該調用。當調用完成時,goroutine 將無聲地退出。(效果類似于Unix shell的&符號,用于在后臺運行命令。)

    go list.Sort() // run list.Sort concurrently; don't wait for it.

    function literal在goroutine調用中很方便。

    func Announce(message string, delay time.Duration) {
        go func() {
            time.Sleep(delay)
            fmt.Println(message)
        }()  // Note the parentheses - must call the function.
    }

    在Go中,函數字面量( function literals )是閉包: 實現確保函數引用的變量只要處于活動狀態就能存活。

    3、Channels

    與map一樣,通道也使用make進行分配,結果值作為對底層數據結構的引用。如果提供了可選的整數參數,它將設置通道的緩沖區大小。對于無緩沖通道或同步通道,默認值為0。

    ci := make(chan int)            // unbuffered channel of integers
    cj := make(chan int, 0)         // unbuffered channel of integers
    cs := make(chan *os.File, 100)  // buffered channel of pointers to Files

    無緩沖通道將通信(值的交換)與同步結合起來,確保兩個計算(gorout例程)處于已知狀態。

    有很多使用通道的好習語。這是一個開始。在前一節中,我們在后臺啟動了排序。通道可以允許啟動goroutine等待排序完成。

    c := make(chan int)  // Allocate a channel.
    // Start the sort in a goroutine; when it completes, signal on the channel.
    go func() {
        list.Sort()
        c <- 1  // Send a signal; value does not matter.
    }()
    doSomethingForAWhile()
    <-c   // Wait for sort to finish; discard sent value.

    接收者總是阻塞,直到有數據接收。如果通道無緩沖,發送方將阻塞,直到接收方接收到該值。如果通道有緩沖區,發送方只阻塞直到值被復制到緩沖區;如果緩沖區已滿,這意味著需要等待到某個接收器接收到一個值。 (參考3.1)

    有緩沖通道可以像信號量(semaphore)一樣使用,例如限制吞吐量。在本例中,傳入的請求被傳遞給handle, handle將一個值發送到通道中,處理請求,然后從通道接收一個值,以便為下一個使用者準備“信號量”。通道緩沖區的容量限制了要處理的同時調用的數量。

    var sem = make(chan int, MaxOutstanding)
    func handle(r *Request) {
        sem <- 1    // Wait for active queue to drain.
        process(r)  // May take a long time.
        <-sem       // Done; enable next request to run.
    }
    func Serve(queue chan *Request) {
        for {
            req := <-queue
            go handle(req)  // Don't wait for handle to finish.
        }
    }

    一旦MaxOutstanding處理程序正在執行進程,試圖向已充滿的通道緩沖區發送的請求都將阻塞,直到現有的一個處理程序完成并從緩沖區接收。

    但是,這種設計有一個問題:Serve為每個傳入的請求創建一個新的goroutine ,盡管在任何時候, 只有MaxOutstanding多個可以運行。因此,如果請求來得太快,程序可能會消耗無限的資源。我們可以通過更改Serve來限制goroutines的創建來解決這個缺陷。這里有一個明顯的解決方案,但要注意它有一個bug,我們隨后會修復:

    func Serve(queue chan *Request) {
        for req := range queue {
            sem <- 1
            go func() {
                process(req) // Buggy; see explanation below.
                <-sem
            }()
        }
    }

    bug 在于,在Go for循環中,循環變量在每次迭代中都被重用,因此req變量在所有goroutine中共享。這不是我們想要的。我們需要確保每個goroutine的req是唯一的。這里有一種方法,在goroutine中將req的值作為參數傳遞給閉包:

    func Serve(queue chan *Request) {
        for req := range queue {
            sem <- 1
            go func(req *Request) {
                process(req)
                <-sem
            }(req)
        }
    }

    將此版本與前一個版本進行比較,查看閉包的聲明和運行方式的差異。另一個解決方案是創建一個同名的新變量,如下例所示:

    func Serve(queue chan *Request) {
        for req := range queue {
            req := req // Create new instance of req for the goroutine.
            sem <- 1
            go func() {
                process(req)
                <-sem
            }()
        }
    }

    這樣寫似乎有些奇怪

    req := req

    但在Go 中這樣做是合法的和慣用的。您將得到一個具有相同名稱的新變量,故意在局部掩蓋循環變量,但對每個goroutine都是惟一的。

    回到編寫服務器的一般問題,另一種很好地管理資源的方法是啟動固定數量的handle goroutines ,所有這些handle goroutines 都從請求通道讀取。goroutine的數量限制了process同時調用的數量。這個Serve函數還接受一個通道,它將被告知退出該通道;在啟動goroutines之后,它會阻止從該通道接收。

    func handle(queue chan *Request) {
        for r := range queue {
            process(r)
        }
    }
    func Serve(clientRequests chan *Request, quit chan bool) {
        // Start handlers
        for i := 0; i < MaxOutstanding; i++ {
            go handle(clientRequests)
        }
        <-quit  // Wait to be told to exit.
    }

    3.1 Channel都有哪些特性

    Go語言中的channel具有以下幾個特性:

    線程安全

    channel是線程安全的,多個協程可以同時讀寫一個channel,而不會發生數據競爭的問題。這是因為Go語言中的channel內部實現了鎖機制,保證了多個協程之間對channel的訪問是安全的。

    阻塞式發送和接收

    當一個協程向一個channel發送數據時,如果channel已經滿了,發送操作會被阻塞,直到有其他協程從channel中取走了數據。同樣地,當一個協程從一個channel中接收數據時,如果channel中沒有數據可供接收,接收操作會被阻塞,直到有其他協程向channel中發送了數據。這種阻塞式的機制可以保證協程之間的同步和通信。

    順序性

    通過channel發送的數據是按照發送的順序進行排列的。也就是說,如果協程A先向channel中發送了數據x,而協程B再向channel中發送了數據y,那么從channel中接收數據時,先接收到的一定是x,后接收到的一定是y。

    可以關閉

    通過關閉channel可以通知其他協程這個channel已經不再使用了。關閉一個channel之后,其他協程仍然可以從中接收數據,但是不能再向其中發送數據了。關閉channel的操作可以避免內存泄漏等問題。

    緩沖區大小

    channel可以帶有一個緩沖區,用于存儲一定量的數據。如果緩沖區已經滿了,發送操作會被阻塞,直到有其他協程從channel中取走了數據;如果緩沖區已經空了,接收操作會被阻塞,直到有其他協程向channel中發送了數據。緩沖區的大小可以在創建channel時指定,例如:

    ch := make(chan int, 10)

    會panic的幾種情況

    1.向已經關閉的channel發送數據

    2.關閉已經關閉的channel

    3.關閉未初始化的nil channel

    會阻塞的情況:

    1.從未初始化 nil channel中讀數據

    2.向未初始化 nil channel中發數據

    3.在沒有讀取的groutine時,向無緩沖channel發數據,

    有緩沖區,但緩沖區已滿,發送數據時

    4.在沒有數據時,從無緩沖或者有緩沖channel讀數據

    返回零值:

    從已經關閉的channe接收數據

    3.2 channel 的最佳實踐

    在使用channel時,應該遵循以下幾個最佳實踐:

    避免死鎖

    使用channel時應該注意避免死鎖的問題。如果一個協程向一個channel發送數據,但是沒有其他協程從channel中取走數據,那么發送操作就會一直被阻塞,從而導致死鎖。為了避免這種情況,可以使用select語句來同時監聽多個channel,從而避免阻塞。

    避免泄漏

    在使用channel時應該注意避免內存泄漏的問題。如果一個channel沒有被關閉,而不再使用了,那么其中的數據就無法被釋放,從而導致內存泄漏。為了避免這種情況,可以在協程結束時關閉channel。

    避免競爭

    在使用channel時應該注意避免數據競爭的問題。如果多個協程同時讀寫一個channel,那么就可能會發生競爭條件,從而導致數據不一致的問題。為了避免這種情況,可以使用鎖機制或者使用單向channel來限制協程的訪問權限。

    避免過度使用

    在使用channel時應該注意避免過度使用的問題。如果一個程序中使用了大量的channel,那么就可能會導致程序的性能下降。為了避免這種情況,可以使用其他的并發編程機制,例如鎖、條件變量等。

    4、Channels of channels

    Go最重要的屬性之一是通道是first-class值,可以像其他值一樣分配和傳遞。此屬性的常見用途是實現安全的并行多路解復用。

    在上一節的示例中,handle是請求的理想處理程序,但我們沒有定義它處理的類型。如果該類型包含要在其上回復的通道,則每個客戶機都可以為應答提供自己的路徑。下面是Request類型的示意圖定義。

    type Request struct {
        args        []int
        f           func([]int) int
        resultChan  chan int
    }

    客戶端提供了一個函數及其參數,以及請求對象內用于接收answer的通道。

    func sum(a []int) (s int) {
        for _, v := range a {
            s += v
        }
        return
    }
    request := &Request{[]int{3, 4, 5}, sum, make(chan int)}
    // Send request
    clientRequests <- request
    // Wait for response.
    fmt.Printf("answer: %d\n", <-request.resultChan)

    在服務器端,唯一需要更改的是處理程序函數。

    func handle(queue chan *Request) {
        for req := range queue {
            req.resultChan <- req.f(req.args)
        }
    }

    顯然,要實現它還有很多工作要做,但這段代碼是一個速率受限、并行、非阻塞RPC系統的框架,而且還沒有看到mutex 。

    5、并行(Parallelization)

    這些思想的另一個應用是跨多個CPU核并行計算。如果計算可以被分解成可以獨立執行的獨立部分,那么它就可以被并行化,并在每個部分完成時用一個通道發出信號。

    假設我們有一個昂貴的操作要對一個items的向量執行,并且每個item的操作值是獨立的,就像在這個理想的例子中一樣。

    type Vector []float64
    // Apply the operation to v[i], v[i+1] ... up to v[n-1].
    func (v Vector) DoSome(i, n int, u Vector, c chan int) {
        for ; i < n; i++ {
            v[i] += u.Op(v[i])
        }
        c <- 1    // signal that this piece is done
    }

    我們在一個循環中獨立地啟動這些片段,每個CPU一個。它們可以按任何順序完成,但這沒有關系;我們只是在啟動所有的goroutine之后通過排泄通道來計算完成信號。

    const numCPU = 4 // number of CPU cores
    func (v Vector) DoAll(u Vector) {
        c := make(chan int, numCPU)  // Buffering optional but sensible.
        for i := 0; i < numCPU; i++ {
            go v.DoSome(i*len(v)/numCPU, (i+1)*len(v)/numCPU, u, c)
        }
        // Drain the channel.
        for i := 0; i < numCPU; i++ {
            <-c    // wait for one task to complete
        }
        // All done.
    }

    我們不需要為numCPU創建一個常量,而是可以詢問運行時哪個值是合適的。函數runtime.NumCPU返回機器中硬件CPU核數,因此我們可以這樣寫

    還有一個函數 runtime.GOMAXPROCS,它報告(或設置)用戶指定的Go程序可以同時運行的核數。默認值為runtime.NumCPU,但可以通過設置類似命名的shell環境變量或調用帶有正數的函數來覆蓋。用0調用它只是查詢值。因此,如果我們想要滿足用戶的資源請求,我們應該寫

    var numCPU = runtime.GOMAXPROCS(0)

    請務必不要混淆并發性(concurrency,將程序構造為獨立執行的組件)和并行性(parallelism, 在多個cpu上并行執行計算以提高效率)這兩個概念。盡管Go的并發特性可以使一些問題很容易構建為并行計算,但Go是一種并發語言,而不是并行語言,并且并不是所有的并行化問題都適合Go的模型。關于區別的討論,請參閱本文章中引用的談話。

    6、漏桶緩沖區(A leaky buffer)

    并發編程的工具甚至可以使非并發的想法更容易表達。下面是一個從RPC包中抽象出來的示例。客戶端goroutine循環從某個源(可能是網絡)接收數據。為了避免分配和釋放緩沖區,它保留了一個空閑列表,并使用緩沖通道來表示它。如果通道為空,則分配一個新的緩沖區。一旦消息緩沖區準備好了,它就被發送到serverChan上的服務器。

    var freeList = make(chan *Buffer, 100)
    var serverChan = make(chan *Buffer)
    func client() {
        for {
            var b *Buffer
            // Grab a buffer if available; allocate if not.
            select {
            case b = <-freeList:
                // Got one; nothing more to do.
            default:
                // None free, so allocate a new one.
                b = new(Buffer)
            }
            load(b)              // Read next message from the net.
            serverChan <- b      // Send to server.
        }
    }

    服務器循環從客戶端接收每條消息,處理它,并將緩沖區返回到空閑列表。

    func server() {
        for {
            b := <-serverChan    // Wait for work.
            process(b)
            // Reuse buffer if there's room.
            select {
            case freeList <- b:
                // Buffer on free list; nothing more to do.
            default:
                // Free list full, just carry on.
            }
        }
    }

    客戶端嘗試從freeList中檢索緩沖區;如果沒有可用的,則分配一個新的。服務器發送給freeList的消息會將b放回空閑列表中,除非空閑列表已滿,在這種情況下,緩沖區將被丟棄在地板上,由垃圾收集器回收。(當沒有其他case可用時,select語句中的default 子句將執行,這意味著select語句永遠不會阻塞。)此實現僅用幾行就構建了一個漏桶列表,依賴于緩沖通道和垃圾收集器進行記賬。

    感謝各位的閱讀,以上就是“Golang并發編程怎么應用”的內容了,經過本文的學習后,相信大家對Golang并發編程怎么應用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

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

    AI

    原平市| 西华县| 蕉岭县| 大方县| 九龙坡区| 武隆县| 威海市| 辉县市| 准格尔旗| 嘉祥县| 涞水县| 长沙市| 安平县| 拜城县| 平度市| 通渭县| 成都市| 镇原县| 秦安县| 台东市| 宽甸| 土默特左旗| 宁南县| 昭苏县| 聊城市| 全南县| 白水县| 西吉县| 华蓥市| 龙游县| 浮梁县| 蒙城县| 肇源县| 灵寿县| 富民县| 睢宁县| 宾川县| 山西省| 吉安市| 峨眉山市| 木兰县|