欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你了解Golang中的并發(fā)性

 更新時間:2023年03月15日 11:25:43   作者:洛天楓  
并發(fā)是一個很酷的話題,一旦你掌握了它,就會成為一筆巨大的財富。所以本文就來和大家一起來聊聊Golang中的并發(fā)性,感興趣的可以了解一下

并發(fā)是一個很酷的話題,一旦你掌握了它,就會成為一筆巨大的財富。說實話,我一開始很害怕寫這篇文章,因為我自己直到最近才對并發(fā)性不太適應(yīng)。我已經(jīng)掌握了基礎(chǔ)知識,所以我想幫助其他初學(xué)者學(xué)習(xí)Go的并發(fā)性。這是眾多并發(fā)性教程中的第一篇,請繼續(xù)關(guān)注更多的教程。

什么是并發(fā)性,為什么它很重要

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

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

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

并發(fā)性與平行性

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

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

Goroutines, the worker Mortys

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

讓我們從一個連續(xù)的代碼開始。

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

上面的代碼打印出當(dāng)前時間和一個字符串。每條打印語句的運行時間為一秒??偟膩碚f,這段代碼大約需要三秒鐘的時間來完成。

現(xiàn)在讓我們把它與一個并發(fā)的代碼進行比較。

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,分別打印當(dāng)前時間和i。這段代碼花了大約一秒鐘完成。這比順序版本快了三倍左右。

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

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

嗯......。程序確實在沒有任何慌亂的情況下退出了,但我們?nèi)鄙賮碜詆oroutines的輸出。為什么它們被跳過?

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

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

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

總結(jié)一下,我們學(xué)到了這些東西:

  • 工作被委托給goroutines。
  • 使用并發(fā)性可以提高你的性能。
  • 主goroutine默認(rèn)不等待工作goroutine完成。
  • 我們需要一種方法來等待每個goroutine完成。

Channels, the green portal

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

ch := make(chan int)

每個通道都是強類型的,并且只允許該類型的數(shù)據(jù)通過。讓我們看看我們?nèi)绾问褂眠@個。

func main() {
    unbufferedCh()
}

func unbufferedCh() {
    ch := make(chan int)

    go func() {
        ch <- 1
    }()

    res := <-ch
    fmt.Println(res)
}

1

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

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

func main() {
    unbufferedChFail()
}

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

fatal error: all goroutines are asleep - deadlock!

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

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

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

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

另一種從通道接收而不發(fā)生死鎖的方法是先關(guān)閉通道。

func main() {
    unbufferedCh()
}

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

0

關(guān)閉通道意味著不能再向它發(fā)送數(shù)據(jù)。我們?nèi)匀豢梢詮脑撏ǖ乐薪邮账τ谖淳彌_的通道,從一個關(guān)閉的通道接收將返回一個通道類型的零值。

總結(jié)一下,我們學(xué)到了這些東西:

  • 通道是goroutines之間相互交流的方式。
  • 你可以通過通道發(fā)送和接收數(shù)據(jù)。
  • 通道是強類型的。
  • 沒有緩沖的通道沒有空間來存儲數(shù)據(jù),所以發(fā)送和接收必須同時進行。否則,你的代碼就會陷入死鎖。
  • 一個封閉的通道將不接受任何數(shù)據(jù)。
  • 從一個封閉的非緩沖通道接收數(shù)據(jù)將返回一個零值。

如果通道能保持?jǐn)?shù)據(jù)一段時間,那不是很好嗎?這里就是緩沖通道發(fā)揮作用的地方。

Buffered channels, the portal that is somehow cylindrical?

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

func main() {
    bufferedCh()
}

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

1

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

很明顯,我們不能向一個滿了緩沖區(qū)的通道發(fā)送更多的信息。你需要在緩沖區(qū)內(nèi)有空間才能發(fā)送更多。

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!

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

func main() {
    bufferedCh2()
}

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

1

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

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

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

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

總結(jié)一下:

  • 緩沖通道是有空間容納項目的通道。
  • 發(fā)送和接收不一定要同時進行,與非緩沖通道不同。
  • 向一個滿的通道發(fā)送和從一個空的通道接收將導(dǎo)致一個死鎖。
  • 你可以在一個封閉的通道上進行迭代,以接收緩沖區(qū)內(nèi)的剩余值。

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

通道可以用來同步goroutines。還記得我告訴過你,在通過無緩沖通道傳輸數(shù)據(jù)之前,發(fā)送方和接收方必須都準(zhǔn)備好了嗎?這意味著接收方將等待,直到發(fā)送方準(zhǔn)備好。我們可以說,接收是阻斷的,意思是接收方將阻斷其他代碼的運行,直到它收到東西。讓我們用這個巧妙的技巧來同步我們的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通道,負(fù)責(zé)阻斷代碼,直到goroutine完成。done可以是任何類型,但struct{}經(jīng)常被用于這些類型的通道。它的目的不是為了傳輸結(jié)構(gòu),所以它的類型并不重要。

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

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

總結(jié)

并發(fā)可能看起來是一個令人生畏的話題。我當(dāng)然認(rèn)為是這樣的。然而,在了解了基礎(chǔ)知識之后,我認(rèn)為實現(xiàn)起來真的很美。希望你們能從這個教程中有所收獲我們僅僅是觸及了表面,Go為我們提供的東西還有很多。下一次我們將在更多的并發(fā)性教程中見面。再見!

到此這篇關(guān)于一文帶你了解Golang中的并發(fā)性的文章就介紹到這了,更多相關(guān)Golang并發(fā)性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go gin框架處理panic的方法詳解

    Go gin框架處理panic的方法詳解

    本文我們介紹下recover在gin框架中的應(yīng)用, 首先,在golang中,如果在子協(xié)程中遇到了panic,那么主協(xié)程也會被終止,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下
    2023-09-09
  • Go單例模式與Once源碼實現(xiàn)

    Go單例模式與Once源碼實現(xiàn)

    這篇文章主要介紹了Go單例模式與Once源碼實現(xiàn),本文結(jié)合示例代碼給大家講解的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-12-12
  • Golang對sqlite3數(shù)據(jù)庫進行操作實踐記錄

    Golang對sqlite3數(shù)據(jù)庫進行操作實踐記錄

    sqlite是嵌入式關(guān)系型數(shù)據(jù)庫引擎,官方描述為自包含的、無服務(wù)的、零配置并支持事務(wù)的關(guān)系型數(shù)據(jù)庫引擎,下面這篇文章主要給大家介紹了關(guān)于Golang對sqlite3數(shù)據(jù)庫進行操作的相關(guān)資料,需要的朋友可以參考下
    2024-03-03
  • Golang泛型的使用方法詳解

    Golang泛型的使用方法詳解

    這篇文章主要介紹了Golang中泛型的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • golang基礎(chǔ)之Interface接口的使用

    golang基礎(chǔ)之Interface接口的使用

    這篇文章主要介紹了golang基礎(chǔ)之Interface接口的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07
  • 搭建Go語言的ORM框架Gorm的具體步驟(從Java到go)

    搭建Go語言的ORM框架Gorm的具體步驟(從Java到go)

    很多朋友不知道如何使用Goland軟件,搭建一個ORM框架GORM,今天小編給大家分享一篇教程關(guān)于搭建Go語言的ORM框架Gorm的具體步驟(從Java到go),感興趣的朋友跟隨小編一起學(xué)習(xí)下吧
    2022-09-09
  • golang生成指定位數(shù)的隨機數(shù)的方法

    golang生成指定位數(shù)的隨機數(shù)的方法

    這篇文章主要介紹了golang生成指定位數(shù)的隨機數(shù)的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Go通過不變性優(yōu)化程序詳解

    Go通過不變性優(yōu)化程序詳解

    這篇文章主要為大家介紹了Go通過不變性優(yōu)化程序?qū)嵗斀?,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • golang新手們?nèi)菀追傅?個錯誤總結(jié)

    golang新手們?nèi)菀追傅?個錯誤總結(jié)

    這篇文章主要給大家介紹了關(guān)于golang新手們?nèi)菀追傅?個錯誤,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • golang之反射和斷言的具體使用

    golang之反射和斷言的具體使用

    這篇文章主要介紹了golang之反射和斷言的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10

最新評論