Go學習筆記4

来源:https://www.cnblogs.com/Gao-yubo/archive/2023/09/09/17689986.html
-Advertisement-
Play Games

0 前言 註冊中心不應僅提供服務註冊和發現功能,還應保證對服務可用性監測,對不健康的服務和過期的進行標識或剔除,維護實例的生命周期,以保證客戶端儘可能的查詢到可用的服務列表。 因此本文介紹Nacos註冊中心的健康檢查機制。 1 註冊中心的健康檢查機制 知道⼀個服務是否還健康的方式: 客戶端主動上報, ...


十三、對象

9.挎包創建結構體實例

【1】創建不同的包:

image-20230908174309371

【2】student.go:

image-20230908174316139

【3】main.go:

image-20230908174320728

發現:如果結構體首字母大寫的話,在其它包下可以訪問
但是:如果結構體的首字母小寫?

image-20230908174328206

解決:結構體首字母小寫,跨包訪問沒問題:---》工廠模式

image-20230908174332174 image-20230908174336015

10.封裝

【1】什麼是封裝:
封裝(encapsulation)就是把抽象出的欄位和對欄位的操作封裝在一起,數據被保護在內部,程式的其它包只有通過被授權的操作方法,才能對欄位進行操作。

【2】封裝的好處:

  1. 隱藏實現細節
  2. 提可以對數據進行驗證,保證安全合理

【3】Golang中如何實現封裝:

  1. 建議將結構體、欄位(屬性)的首字母小寫(其它包不能使用,類似private,實際開發不小寫也可能,因為封裝沒有那麼嚴格)
  2. 給結構體所在包提供一個工廠模式的函數,首字母大寫(類似一個構造函數)
  3. 提供一個首字母大寫的Set方法(類似其它語言的public),用於對屬性判斷並賦值
    func (var 結構體類型名)SetXxx(參數列表){
    //加入數據驗證的業務邏輯
    var.Age =參數
    }
  4. 提供一個首字母大寫的Get方法(類似其它語言的public),用於獲取屬性的值
    func (var結構體類型名) GetXxx() (返回值列表){
    return var.欄位;
    }
    【4】代碼實現:
image-20230908174353631 image-20230908174356625 image-20230908174402616

11.繼承

【1】繼承的引入:
當多個結構體存在相同的屬性(欄位)和方法時,可以從這些結構體中抽象出結構體,在該結構體中定義這些相同的屬性和方法,其它的結構體不需要重新定義這些屬性和方法,只需嵌套一個匿名結構體即可。也就是說:在Golang中,如果一個struct嵌套了另一個匿名結構體,那麼這個結構體可以直接訪問匿名結構體的欄位和方法,從而實現了繼承特性。

image-20230908174440091

【2】代碼引入:

package main
import (
        "fmt"
)
//定義動物結構體:
type Animal struct{
        Age int
        Weight float32
}
//給Animal綁定方法:喊叫:
func (an *Animal) Shout(){
        fmt.Println("我可以大聲喊叫")
}
////給Animal綁定方法:自我展示:
func (an *Animal) ShowInfo(){
        fmt.Printf("動物的年齡是:%v,動物的體重是:%v",an.Age,an.Weight)
}
//定義結構體:Cat
type Cat struct{
        //為了復用性,體現繼承思維,嵌入匿名結構體:——》將Animal中的欄位和方法都達到復用
        Animal
}
//對Cat綁定特有的方法:
func (c *Cat) scratch(){
        fmt.Println("我是小貓,我可以撓人")
}
func main(){
        //創建Cat結構體示例:
        cat := &Cat{}
        cat.Animal.Age = 3
        cat.Animal.Weight = 10.6
        cat.Animal.Shout()
        cat.Animal.ShowInfo()
        cat.scratch()
}


【3】繼承的優點:
提高代碼的復用性、擴展性

12.繼承的註意事項

【1】結構體可以使用嵌套匿名結構體所有的欄位和方法,即:首字母大寫或者小寫的欄位、方法,都可以使用。

 package main
import (
        "fmt"
)
//定義動物結構體:
type Animal struct{
        Age int
        weight float32
}
//給Animal綁定方法:喊叫:
func (an *Animal) Shout(){
        fmt.Println("我可以大聲喊叫")
}
////給Animal綁定方法:自我展示:
func (an *Animal) showInfo(){
        fmt.Printf("動物的年齡是:%v,動物的體重是:%v",an.Age,an.weight)
}
//定義結構體:Cat
type Cat struct{
        //為了復用性,體現繼承思維,嵌入匿名結構體:——》將Animal中的欄位和方法都達到復用
        Animal
}
//對Cat綁定特有的方法:
func (c *Cat) scratch(){
        fmt.Println("我是小貓,我可以撓人")
}
func main(){
        //創建Cat結構體示例:
        cat := &Cat{}
        cat.Animal.Age = 3
        cat.Animal.weight = 10.6
        cat.Animal.Shout()
        cat.Animal.showInfo()
        cat.scratch()
}

【2】匿名結構體欄位訪問可以簡化。

等價於:

image-20230908174532157

cat.Age --->cat對應的結構體中找是否有Age欄位,如果有直接使用,如果沒有就去找嵌入的結構體類型中的Age
【3】當結構體和匿名結構體有相同的欄位或者方法時,編譯器採用就近訪問原則訪問,如希望訪問匿名結構體的欄位和方法,可以通過匿名結構體名來區分。

package main
import (
        "fmt"
)
//定義動物結構體:
type Animal struct{
        Age int
        weight float32
}
//給Animal綁定方法:喊叫:
func (an *Animal) Shout(){
        fmt.Println("我可以大聲喊叫")
}
////給Animal綁定方法:自我展示:
func (an *Animal) showInfo(){
        fmt.Printf("動物的年齡是:%v,動物的體重是:%v",an.Age,an.weight)
}
//定義結構體:Cat
type Cat struct{
        //為了復用性,體現繼承思維,嵌入匿名結構體:——》將Animal中的欄位和方法都達到復用
        Animal
        Age int
}
func (c *Cat) showInfo(){
        fmt.Printf("~~~~~~~~動物的年齡是:%v,動物的體重是:%v",c.Age,c.weight)
}
//對Cat綁定特有的方法:
func (c *Cat) scratch(){
        fmt.Println("我是小貓,我可以撓人")
}
func main(){
        //創建Cat結構體示例:
        // cat := &Cat{}
        // cat.Age = 3
        // cat.weight = 10.6
        // cat.Shout()
        // cat.showInfo()
        // cat.scratch()
        cat := &Cat{}
        cat.weight = 9.4
        cat.Age = 10 //就近原則
        cat.Animal.Age = 20
        cat.showInfo()//就近原則
        cat.Animal.showInfo()
}

【4】Golang中支持多繼承:如一個結構體嵌套了多個匿名結構體,那麼該結構體可以直接訪問嵌套的匿名結構體的欄位和方法,從而實現了多重繼承。為了保證代碼的簡潔性,建議大家儘量不使用多重繼承,很多語言就將多重繼承去除了,但是Go中保留了。

package main
import (
        "fmt"
)
type A struct{
        a int
        b string
}
type B struct{
        c int
        d string
}
type C struct{
        A
        B
}
func main(){
        //構建C結構體實例:
        c := C{A{10,"aaa"},B{20,"ccc"}}
        fmt.Println(c)
}

【5】如嵌入的匿名結構體有相同的欄位名或者方法名,則在訪問時,需要通過匿名結構體類型名來區分。

package main
import (
        "fmt"
)
type A struct{
        a int
        b string
}
type B struct{
        c int
        d string
        a int
}
type C struct{
        A
        B
}
func main(){
        //構建C結構體實例:
        c := C{A{10,"aaa"},B{20,"ccc",50}}
        fmt.Println(c.b)
        fmt.Println(c.d)
        fmt.Println(c.A.a)
        fmt.Println(c.B.a)
}

【6】結構體的匿名欄位可以是基本數據類型。

image-20230908174617016

【7】嵌套匿名結構體後,也可以在創建結構體變數(實例)時,直接指定各個匿名結構體欄位的值。

image-20230908174621439

【8】嵌入匿名結構體的指針也是可以的。

image-20230908174628111

【9】結構體的欄位可以是結構體類型的。(組合模式)

image-20230908174656306

13.介面

【1】代碼入門:

package main
import "fmt"
//介面的定義:定義規則、定義規範,定義某種能力:
type SayHello interface{
        //聲明沒有實現的方法:
        sayHello()
}
//介面的實現:定義一個結構體:
//中國人:
type Chinese struct{
}
//實現介面的方法---》具體的實現:
func (person Chinese) sayHello(){
        fmt.Println("你好")
}
//介面的實現:定義一個結構體:
//美國人:
type American struct{
}
//實現介面的方法---》具體的實現:
func (person American) sayHello(){
        fmt.Println("hi")
}
//定義一個函數:專門用來各國人打招呼的函數,接收具備SayHello介面的能力的變數:
func greet(s SayHello){
        s.sayHello()
}
func main(){
        //創建一個中國人:
        c := Chinese{}
        //創建一個美國人:
        a := American{}
        //美國人打招呼:
        greet(a)
        //中國人打招呼:
        greet(c)
}

【2】總結:
(1)介面中可以定義一組方法,但不需要實現,不需要方法體。並且介面中不能包含任何變數。到某個自定義類型要使用的時候(實現介面的時候),再根據具體情況把這些方法具體實現出來。
(2)實現介面要實現所有的方法才是實現。
(3)Golang中的介面不需要顯式的實現介面。Golang中沒有implement關鍵字。
(Golang中實現介面是基於方法的,不是基於介面的)
例如:
A介面 a,b方法
B介面 a,b方法
C結構體 實現了 a,b方法 ,那麼C實現了A介面,也可以說實現了B介面 (只要實現全部方法即可,和實際介面耦合性很低,比Java鬆散得多)
(4)介面目的是為了定義規範,具體由別人來實現即可。

14.介面註意事項

【1】介面本身不能創建實例,但是可以指向一個實現了該介面的自定義類型的變數。

image-20230908174948195

【2】只要是自定義數據類型,就可以實現介面,不僅僅是結構體類型。

image-20230908174952128

【3】一個自定義類型可以實現多個介面

package main
import "fmt"
type AInterface interface{
        a()
}
type BInterface interface{
        b()
}
type Stu struct{
}
func (s Stu) a(){
        fmt.Println("aaaa")
}
func (s Stu) b(){
        fmt.Println("bbbb")
}
func main(){
        var s Stu
        var a AInterface = s
    var b BInterface = s
        a.a()
        b.b()
}

【4】一個介面(比如A介面)可以繼承多個別的介面(比如B,C介面),這時如果要實現A介面,也必須將B,C介面的方法也全部實現。

package main
import "fmt"
type CInterface interface{
        c()
}
type BInterface interface{
        b()
}
type AInterface interface{
        BInterface
        CInterface
        a()
}
type Stu struct{
}
func (s Stu) a(){
        fmt.Println("a")
}
func (s Stu) b(){
        fmt.Println("b")
}
func (s Stu) c(){
        fmt.Println("c")
}
func main(){
        var s Stu
        var a AInterface = s
        a.a()
        a.b()
        a.c()
}

【5】interface類型預設是一個指針(引用類型),如果沒有對interface初始化就使用,那麼會輸出nil

【6】空介面沒有任何方法,所以可以理解為所有類型都實現了空介面,也可以理解為我們可以把任何一個變數賦給空介面。

image-20230908174938019

15.多態

【1】基本介紹
變數(實例)具有多種形態。面向對象的第三大特征,在Go語言,多態特征是通過介面實現的。可以按照統一的介面來調用不同的實現。這時介面變數就呈現不同的形態。

【2】案例:

image-20230908193450698

【3】介面體現多態特征

  1. 多態參數: s叫多態參數

image-20230908193455554

  1. 多態數組 :
    比如:定義SayHello數組,存放中國人結構體、美國人結構體

image-20230908193459516

16.斷言

Go語言裡面有一個語法,可以直接判斷是否是該類型的變數: value, ok := element.(T).

這裡value就是變數的值,ok是一個bool類型,element是interface變數,T是斷言的類型。

【2】斷言的案例引入:

package main
import "fmt"
//介面的定義:定義規則、定義規範,定義某種能力:
type SayHello interface{
        //聲明沒有實現的方法:
        sayHello()
}
//介面的實現:定義一個結構體:
//中國人:
type Chinese struct{
        name string
}
//實現介面的方法---》具體的實現:
func (person Chinese) sayHello(){
        fmt.Println("你好")
}
//中國人特有的方法
func (person Chinese) niuYangGe(){
        fmt.Println("東北文化-扭秧歌")
}
//介面的實現:定義一個結構體:
//美國人:
type American struct{
        name string
}
//實現介面的方法---》具體的實現:
func (person American) sayHello(){
        fmt.Println("hi")
}
//定義一個函數:專門用來各國人打招呼的函數,接收具備SayHello介面的能力的變數:
func greet(s SayHello){
        s.sayHello()
        //斷言:
        var ch Chinese = s.(Chinese)//看s是否能轉成Chinese類型並且賦給ch變數
        ch.niuYangGe()
}
func main(){       

    //創建一個中國人:
    c := Chinese{}
    //創建一個美國人:
    //a := American{}
    //美國人打招呼:
    //greet(a)
    //中國人打招呼:
    greet(c)

}

解決第二個返回值問題:

package main
import "fmt"
//介面的定義:定義規則、定義規範,定義某種能力:
type SayHello interface{
        //聲明沒有實現的方法:
        sayHello()
}
//介面的實現:定義一個結構體:
//中國人:
type Chinese struct{
        name string
}
//實現介面的方法---》具體的實現:
func (person Chinese) sayHello(){
        fmt.Println("你好")
}
//中國人特有的方法
func (person Chinese) niuYangGe(){
        fmt.Println("東北文化-扭秧歌")
}
//介面的實現:定義一個結構體:
//美國人:
type American struct{
        name string
}
//實現介面的方法---》具體的實現:
func (person American) sayHello(){
        fmt.Println("hi")
}
//定義一個函數:專門用來各國人打招呼的函數,接收具備SayHello介面的能力的變數:
func greet(s SayHello){
        s.sayHello()
        //斷言:
        ch,flag := s.(Chinese)//看s是否能轉成Chinese類型並且賦給ch變數,flag是判斷是否轉成功
        if flag == true{
                ch.niuYangGe()
        }else{
                fmt.Println("美國人不會扭秧歌")
        }
        fmt.Println("打招呼。。。")
}
func main(){

        //創建一個中國人:
        //c := Chinese{}
        //創建一個美國人:
        a := American{}
        //美國人打招呼:
        greet(a)
        //中國人打招呼:
        //greet(c)

}

更簡略的語法:

image-20230908193746281

【3】Type Switch 的基本用法
Type Switch 是 Go 語言中一種特殊的 switch 語句,它比較的是類型而不是具體的值。它判斷某個介面變數的類型,然後根據具體類型再做相應處理。

package main
import "fmt"
//介面的定義:定義規則、定義規範,定義某種能力:
type SayHello interface{
        //聲明沒有實現的方法:
        sayHello()
}
//介面的實現:定義一個結構體:
//中國人:
type Chinese struct{
        name string
}
//實現介面的方法---》具體的實現:
func (person Chinese) sayHello(){
        fmt.Println("你好")
}
//中國人特有的方法
func (person Chinese) niuYangGe(){
        fmt.Println("東北文化-扭秧歌")
}
//介面的實現:定義一個結構體:
//美國人:
type American struct{
        name string
}
//實現介面的方法---》具體的實現:
func (person American) sayHello(){
        fmt.Println("hi")
}
func (person American) disco(){
        fmt.Println("野狼disco")
}
//定義一個函數:專門用來各國人打招呼的函數,接收具備SayHello介面的能力的變數:
func greet(s SayHello){
        s.sayHello()
        //斷言:
        // ch,flag := s.(Chinese)//看s是否能轉成Chinese類型並且賦給ch變數,flag是判斷是否轉成功
        // if flag == true{
        // 	ch.niuYangGe()
        // }else{
        // 	fmt.Println("美國人不會扭秧歌")
        // }
        

        // if ch,flag := s.(Chinese);flag{
        // 	ch.niuYangGe()
        // }else{
        // 	fmt.Println("美國人不會扭秧歌")
        // }
        switch s.(type){//type屬於go中的一個關鍵字,固定寫法
                case Chinese:
                        ch := s.(Chinese)
                        ch.niuYangGe()
                case American:
                        am := s.(American)
                        am.disco()
        }
        fmt.Println("打招呼。。。")

}
func main(){
        //創建一個中國人:
        c := Chinese{}
        //創建一個美國人:
        //a := American{}
        //美國人打招呼:
        //greet(a)
        //中國人打招呼:
        greet(c)

}

十四、文件操作

1.文件

【1】文件是什麼?
文件是保存數據的地方,是數據源的一種,比如大家經常使用的word文檔、txt文件、excel文件、jpg文件...都是文件。文件最主要的作用就是保存數據,它既可以保存一張圖片,也可以保持視頻,聲音...
【2】os包下的File結構體封裝了對文件的操作:

image-20230908200611278

【3】File結構體---打開文件和關閉文件:
(1)打開文件,用於讀取:(函數)

image-20230908200617936

傳入一個字元串(文件的路徑),返回的是文件的指針,和是否打開成功

(2)關閉文件:(方法)

使文件不能用於讀寫。它返回可能出現的錯誤

【4】案例:

package main
import(
        "fmt"
        "os"
)
func main(){
        //打開文件:
        file,err := os.Open("d:/Test.txt");
        if err != nil {//出錯
                fmt.Println("文件打開出錯,對應錯誤為:",err)
        }
        //沒有出錯,輸出文件:
        fmt.Printf("文件=%v",file)
        //.........一系列操作
        //關閉文件:
        err2 := file.Close();
        if err2 != nil {
                fmt.Println("關閉失敗")
        }
}

2.IO流的引入

image-20230908200715250

3.讀取文件(一次性)

【1】讀取文件的內容並顯示在終端(使用ioutil一次將整個文件讀入到記憶體中),這種方式適用於文件不大的情況。相關方法和函數(ioutil.ReadFile)

image-20230908200759118

【2】案例:

package main
import(
        "fmt"
        "io/ioutil"
)
func main(){
        //備註:在下麵的程式中不需要進行 Open\Close操作,因為文件的打開和關閉操作被封裝在ReadFile函數內部了
        //讀取文件:
        content,err := ioutil.ReadFile("d:/Test.txt")//返回內容為:[]byte,err
        if err != nil {//讀取有誤
                fmt.Println("讀取出錯,錯誤為:",err)
        }
        //如果讀取成功,將內容顯示在終端即可:
        //fmt.Printf("%v",content)
        fmt.Printf("%v",string(content))
}

4.讀取文件(帶緩衝區)

【1】讀取文件的內容並顯示在終端(帶緩衝區的方式-4096位元組),適合讀取比較大的文件,使用os.Open,file.Close,bufio.NewReader(),reader.ReadString函數和方法

【2】案例:

 package main
import(
        "fmt"
        "os"
        "bufio"
        "io"
)
func main(){
        //打開文件:
        file,err := os.Open("d:/Test.txt")
        if err != nil {//打開失敗
                fmt.Println("文件打開失敗,err=",err)
        }
        //當函數退出時,讓file關閉,防止記憶體泄露:
        defer file.Close()
        //創建一個流:
        reader := bufio.NewReader(file)
        //讀取操作:
        for {
                str,err := reader.ReadString('\n')//讀取到一個換行就結束
                if err == io.EOF {//io.EOF 表示已經讀取到文件的結尾
                        break
                }
                //如果沒有讀取到文件結尾的話,就正常輸出文件內容即可:
                fmt.Println(str)
        }
        //結束:
        fmt.Println("文件讀取成功,並且全部讀取完畢")
}

5.寫入文件

【1】打開文件操作:

image-20230908200944060

三個參數含義:
(1)要打開的文件的路徑

(2)文件打開模式(可以利用"|"符號進行組合)

image-20230908201008260

(3)許可權控制(linux/unix系統下才生效,windows下設置無效)- 0666

【2】案例:

 package main
import(
        "fmt"
        "os"
        "bufio"
)
func main(){
        //寫入文件操作:
        //打開文件:
        file , err := os.OpenFile("d:/Demo.txt",os.O_RDWR | os.O_APPEND | os.O_CREATE,0666)
        if err != nil {//文件打開失敗
                fmt.Printf("打開文件失敗",err)
                return
        }
        //及時將文件關閉:
        defer file.Close()
        //寫入文件操作:---》IO流---》緩衝輸出流(帶緩衝區)
        writer := bufio.NewWriter(file)
        for i := 0; i < 10;i++ {
                writer.WriteString("你好 馬士兵\n")
        } 
        //流帶緩衝區,刷新數據--->真正寫入文件中:
        writer.Flush()
        s :=os.FileMode(0666).String()
        fmt.Println(s)
}

6.文件複製操作

package main
import(
        "fmt"
        "io/ioutil"
)
func main(){
        //定義源文件:
        file1Path := "d:/Demo.txt"
        //定義目標文件:
        file2Path := "d:/Demo2.txt"
        //對文件進行讀取:
        content,err := ioutil.ReadFile(file1Path)
        if err != nil {
                fmt.Println("讀取有問題!")
                return
        }
        //寫出文件:
        err = ioutil.WriteFile(file2Path,content,0666)
        if err != nil {
                fmt.Println("寫出失敗!")
        }
}

十五、協程和管道

1.程式、進程、線程、協程

【1】程式(program)
是為完成特定任務、用某種語言編寫的一組指令的集合,是一段靜態的代碼。 (程式是靜態的)

【2】進程(process)
是程式的一次執行過程。正在運行的一個程式,進程作為資源分配的單位,在記憶體中會為每個進程分配不同的記憶體區域。 (進程是動態的)是一個動的過程 ,進程的生命周期 : 有它自身的產生、存在和消亡的過程

image-20230908212311094

【3】線程(thread)
進程可進一步細化為線程, 是一個程式內部的一條執行路徑。
若一個進程同一時間並行執行多個線程,就是支持多線程的。

image-20230908212328769

【4】協程(goroutine)
又稱為微線程,纖程,協程是一種用戶態的輕量級線程

作用:在執行A函數的時候,可以隨時中斷,去執行B函數,然後中斷繼續執行A函數(可以自動切換),註意這一切換過程並不是函數調用(沒有調用語句),過程很像多線程,然而協程中只有一個線程在執行(協程的本質是個單線程)

image-20230908212442016 對於單線程下,我們不可避免程式中出現io操作,但如果我們能在自己的程式中(即用戶程式級別,而非操作系統級別)控制**單線程下的多**

個任務能在一個任務遇到io阻塞時就將寄存器上下文和棧保存到某個其他地方,然後切換到另外一個任務去計算。

在任務切回來的時候,恢復先前保存的寄存器上下文和棧,這樣就保證了該線程能夠最大限度地處於就緒態.

即隨時都可以被cpu執行的狀態,相當於我們在用戶程式級別將自己的io操作最大限度地隱藏起來,從而可以迷惑操作系統,讓其看到:該線程好像是一直在計算,io比較少,從而會更多的將cpu的執行許可權分配給我們的線程

(註意:線程是CPU控制的,而協程是程式自身控制的,屬於程式級別的切換,操作系統完全感知不到,因而更加輕量級)

2.協程入門

【1】案例:
請編寫一個程式,完成如下功能:
(1)在主線程中,開啟一個goroutine,該goroutine每隔1秒輸出"hello golang"
(2)在主線程中也每隔一秒輸出"hello msb",輸出10次後,退出程式
(3)要求主線程和goroutine同時執行

代碼:

package main
import(
        "fmt"
        "strconv"
        "time"
)
func test(){
        for i := 1;i <= 10;i++ {
                fmt.Println("hello golang + " + strconv.Itoa(i))
                //阻塞一秒:
                time.Sleep(time.Second)
        }
}
func main(){//主線程
        go test() //開啟一個協程
        for i := 1;i <= 10;i++ {
                fmt.Println("hello msb + " + strconv.Itoa(i))
                //阻塞一秒:
                time.Sleep(time.Second)
        }
}

代碼結果:

image-20230908212654090

主線程和協程執行流程示意圖:

image-20230908212658297

3.主死從隨

【1】主死從隨:

  1. 如果主線程退出了,則協程即使還沒有執行完畢,也會退出
  2. 當然協程也可以在主線程沒有退出前,就自己結束了,比如完成了自己的任務
package main
import(
        "fmt"
        "strconv"
        "time"
)
func test(){
        for i := 1;i <= 1000;i++ {
                fmt.Println("hello golang + " + strconv.Itoa(i))
                //阻塞一秒:
                time.Sleep(time.Second)
        }
}
func main(){//主線程
        go test() //開啟一個協程
        for i := 1;i <= 10;i++ {
                fmt.Println("hello msb + " + strconv.Itoa(i))
                //阻塞一秒:
                time.Sleep(time.Second)
        }
}

4.多個協程

package main
import(
        "fmt"
        "time"
)
func main(){
        //匿名函數+外部變數 = 閉包
        for i := 1;i <= 5;i++ {
                //啟動一個協程
                //使用匿名函數,直接調用匿名函數
                go func(n int){
                        fmt.Println(n)
                }(i)
        }
        time.Sleep(time.Second * 2)
}

5.使用WaitGroup控制協程退出

【1】WaitGroup的作用:
WaitGroup用於等待一組線程的結束。父線程調用Add方法來設定應等待的線程的數量。每個被等待的線程在結束時應調用Done方法。同時,主線程里可以調用Wait方法阻塞至所有線程結束。---》解決主線程在子協程結束後自動結束

【2】主要方法:
(1)

image-20230908212952786

(2)

image-20230908213000485

(3)

image-20230908213005299

【3】案例:

(1)Add\Done\Wait:

package main
import(
        "fmt"
        "sync"
)
var wg sync.WaitGroup //只定義無需賦值
func main(){
        //啟動五個協程
        for i := 1 ;i <= 5;i++ {
                wg.Add(1) //協程開始的時候加1操作
                go func(n int){
                        fmt.Println(n)
                        wg.Done()  //協程執行完成減1
                }(i)
        }
        //主線程一直在阻塞,什麼時候wg減為0了,就停止
        wg.Wait()
}

(2)如果防止忘記計數器減1操作,結合defer關鍵字使用:

package main
import(
        "fmt"
        "sync"
)
var wg sync.WaitGroup //只定義無需賦值
func main(){
        //啟動五個協程
        for i := 1 ;i <= 5;i++ {
                wg.Add(1) //協程開始的時候加1操作
                go func(n int){
                        defer wg.Done()
                        fmt.Println(n)		
                }(i)
        }
        //主線程一直在阻塞,什麼時候wg減為0了,就停止
        wg.Wait()
}

(3)可以最開始在知道協程次數的情況下先Add操作:

package main
import(
        "fmt"
        "sync"
)
var wg sync.WaitGroup //只定義無需賦值
func main(){
        wg.Add(5)
        //啟動五個協程
        for i := 1 ;i <= 5;i++ {
                go func(n int){
                        defer wg.Done()
                        fmt.Println(n)		
                }(i)
        }
        //主線程一直在阻塞,什麼時候wg減為0了,就停止
        wg.Wait()
}

註意:Add中加入的數字和協程的次數一定要保持一致

6.多協同操作同一數據(互斥鎖)

【1】案例:多個協程操縱同一數據

package main
import(
        "fmt"
        "sync"
)
//定義一個變數:
var totalNum int
var wg sync.WaitGroup //只定義無需賦值
func add(){
        defer wg.Done()
        for i := 0 ;i < 100000;i++{
                totalNum = totalNum + 1
        }
}
func sub(){
        defer wg.Done()
        for i := 0 ;i < 100000;i++{
                totalNum = totalNum - 1
        }
}
func main(){
        wg.Add(2)
        //啟動協程
        go add()
        go sub()
        wg.Wait()
        fmt.Println(totalNum)
}

結果:在理論上,這個totalNum結果應該是0 ,無論協程怎麼交替執行,最終想象的結果就是0
但是事實上:不是

image-20230908213237923

問題出現的原因:(圖解為其中一種可能性)

image-20230908213242816

解決問題:
有一個機制:確保:一個協程在執行邏輯的時候另外的協程不執行
----》鎖的機制---》加入互斥鎖

image-20230908213250992

代碼:

package main
import(
        "fmt"
        "sync"
)
//定義一個變數:
var totalNum int
var wg sync.WaitGroup //只定義無需賦值
//加入互斥鎖:
var lock sync.Mutex
func add(){
        defer wg.Done()
        for i := 0 ;i < 100000;i++{
                //加鎖
                lock.Lock()
                totalNum = totalNum + 1
                //解鎖:
                lock.Unlock()
        }
}
func sub(){
        defer wg.Done()
        for i := 0 ;i < 100000;i++{
                //加鎖
                lock.Lock()
                totalNum = totalNum - 1
                //解鎖:
                lock.Unlock()
        }
}
func main(){
        wg.Add(2)
        //啟動協程
        go add()
        go sub()
        wg.Wait()
        fmt.Println(totalNum)
}

7.讀寫鎖

golang中sync包實現了兩種鎖Mutex (互斥鎖)和RWMutex(讀寫鎖)
【1】互斥鎖
其中Mutex為互斥鎖,Lock()加鎖,Unlock()解鎖,使用Lock()加鎖後,便不能再次對其進行加鎖,直到利用Unlock()解鎖對其解鎖後,才能再次加鎖.適用於讀寫不確定場景,即讀寫次數沒有明顯的區別
----性能、效率相對來說比較低

【2】讀寫鎖
RWMutex是一個讀寫鎖,其經常用於讀次數遠遠多於寫次數的場景.
---在讀的時候,數據之間不產生影響, 寫和讀之間才會產生影響

【3】案例:

package main
import(
        "fmt"
        "sync"
        "time"
)
var wg sync.WaitGroup //只定義無需賦值
//加入讀寫鎖:
var lock sync.RWMutex
func read(){
        defer wg.Done()
        lock.RLock()//如果只是讀數據,那麼這個鎖不產生影響,但是讀寫同時發生的時候,就會有影響
        fmt.Println("開始讀取數據")
        time.Sleep(time.Second)
        fmt.Println("讀取數據成功")
        lock.RUnlock()
}
func write(){
        defer wg.Done()
        lock.Lock()
        fmt.Println("開始修改數據")
        time.Sleep(time.Second * 10)
        fmt.Println("修改數據成功")
        lock.Unlock()
}
func main(){
        wg.Add(6)
        //啟動協程 ---> 場合:讀多寫少
        for i := 0;i < 5;i++ {
                go read()
        }
        go write()
        wg.Wait()
}

image-20230908213336482


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 防火牆配置 # 啟動防火牆服務 systemctl start firewalld # 關閉防火牆服務 systemctl stop firewalld # 查看防火牆服務狀態 systemctl status firewalld # 開機禁用防火牆服務 systemctl disable fire ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 一. keep-alive 的作用 二. keep-alive 的原理 三. keep-alive 的應用 四. keep-alive 的刷新 五. keep-alive 頁面緩存思路 一. keep-alive 的作用 首先引用官 ...
  • 前段時間和朋友做了一個區域網考試系統,總共有3個端:考生端、監考端、管理端。 框架與相關的庫 先簡單說明一下我使用的框架和相關的庫: 構建工具:Vite 框架:Vue3 UI組件庫:element-plus 網路請求庫:axios 路由跳轉:vue-router 狀態管理:pinia CSS擴展語言 ...
  • >我們是[袋鼠雲數棧 UED 團隊](http://ued.dtstack.cn/),致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 >本文作者:琉易 [liuxianyu.cn](https://link.juejin.cn/?target=ht ...
  • 以下是一個Python實現的簡單二分查找演算法的代碼示例: def binary_search(arr, target): left, right = 0, len(arr) - 1 while left <= right: mid = (left + right) // 2 # 找到中間元素的索引 ...
  • 一條爬蟲抓取一個小網站所有數據 ​ 今天閑來無事,寫一個爬蟲來玩玩。在網上衝浪的時候發現了一個搞笑的段子網,發現裡面的內容還是比較有意思的,於是心血來潮,就想著能不能寫一個Python程式,抓取幾條數據下來看看,一不小心就把這個網站的所有數據都拿到了。 ​ 這個網站主要的數據都是詳情在HTML裡面的 ...
  • 上篇文章12分鐘從Executor自頂向下徹底搞懂線程池中我們聊到線程池,而線程池中包含阻塞隊列 這篇文章我們主要聊聊併發包下的阻塞隊列 阻塞隊列 什麼是隊列? 隊列的實現可以是數組、也可以是鏈表,可以實現先進先出的順序隊列,也可以實現先進後出的棧隊列 那什麼是阻塞隊列? 在經典的生產者/消費者模型 ...
  • 本篇文章深入探討了 Go 語言中類型確定值、類型不確定值以及對應類型轉換的知識點,後續充分解析了常量與變數及其高級用法,並舉出豐富的案例。 關註公眾號【TechLeadCloud】,分享互聯網架構、雲服務技術的全維度知識。作者擁有10+年互聯網服務架構、AI產品研發經驗、團隊管理經驗,同濟本復旦碩, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...