Go工程師體系課 001【學習筆記】

轉型

  1. 想在短時間系統轉到Go工程理由
  2. 提高CRUD,無自研框架經驗
  3. 拔高技術深度,做專、做精需求的同學
  4. 進階工程化,擁有良好開發規範和管理能力的

工程化的重要性

高級開的期望

  1. 良好的代碼規範
  2. 深入底層原理
  3. 熟悉架構
  4. 熟悉k8s的基礎架構

擴展知識廣度,知識的深度,規範的開發體系

四個大的階段

  1. go語言基礎
  2. 微服務開發的(電商項目實戰)
  3. 自研微服務
  4. 自研然後重構

領域

  1. Web開發-gin、beego等
  2. 容器虛擬化-docker、k8s、istio
  3. 中間件- etcd、tidb、influxdb、nsq等
  4. 區塊鏈 以太坊、fabric
  5. 1xAR5 - go-zero, dapr, rpcx, kratos. dubbo-go,

相關環境的開發

  1. go安裝 官網
  2. goland、vscdoe
# go build hello.go
# ./hello
go run hello.go

golang新版本中同一個包下有多個main運行時,運行工具中選擇運行類型file, go中不推薦一個目錄中有兩個main文件,如果需要有多個main放到不同的文件夾中

變量

package main

import "fmt"

// 全局變量 定義可以不使用
var name = "bobby"
var age = 19
var ok bool

func main() {
 // go是靜態語言 靜態語言和動態語言相比變量差異很大
 // 1. 變更定義 先定義後使用 類型聲明瞭不能改變
 // 定義變量
 var name string = "hello world"
 age := 1
 // go 語言中變量定義的不使用是不行的,強制性的
 fmt.Println(name, age)

 // 多變量定義
 var user1, user2, user3 = "booby1", "bobby2", 22
 fmt.Println(user1, user2, user3)

 // 注意變量先定義才能使用
 // 是靜態語言類型和賦值要一致
 // 要求和規範性會更好
 // 變量名不能衝突 同一個代碼塊中不能同名的
 // 簡潔定義 名:=值 不能用於全局定義
 // 變量是有0值的

}

變量

package main

import "fmt"

func main() {
 // 常量 定義的時候就要指定值,不能修改
 const PI float32 = 3.14159265358979323846 // 顯式定義
 const PI2 = 3.14159265358979323846        // 常量 大寫多單詞 用下划線
 const (
  UNKNOW = 1
  FEMALE = 2
  MALE   = 3
 )
 //沒有設置類型和值它會沿用前面的值
 const (
  x int = 16
  y
  s = "abc"
  z
 )
 fmt.Println(x, y, z)
 /**
 常量類型只要以是bool 數值(整數、浮點)
 不曾使用的常量,沒有強制使用的
 顯示指定類型的時候,必須確保左右類型一致
 */
}

iota

特殊常量

package main

import "fmt"

func main() {
 // 匿名變量 定義一個變更不使用它
 var _ int // 接收返回值進,點位符只接收哪個值
 // iota 特殊常量,可以認為是一種可以被編譯器修改的常量
 const (
  ERR1 = iota + 1
  ERR2
  ERR25 = "ha" // iota內部仍然增加計數
  ERR3
  ERR4 = iota
 )
 const (
  ERRORNEW1 = iota // 從0開始計數
 )
 /*
   如果中斷了iota那麼要顯示的恢復,後續會自動遞增 自增類型默認是int類型的
   iota能簡化const類型的定義
 */
 fmt.Println(ERR1, ERR2, ERR3, ERR4)
}

變量的作用域

個人感覺注意代碼塊的範圍還有就是變量名:=值這種方式的聲明

go的基本數據類型

  • 基本數據類型
  • bool true false
  • 數值類型
    • 整數 int8~64 uint8~64(無符號數)
    • 浮點數 float32 float64
    • 複數
    • byte字節 uint8
    • rune類型 int32
  • 字符和string
package main

import "fmt"

func main() {
 var a int8
 var b int16
 var c int32
 var d int64

 var ua uint8
 var ub uint16
 var uc uint32

 var ch3 byte
 c = 'a'         // 字符類型
 var int_32 rune // 也是字符

 int_32 = '中' // 即有中文也有英文
 fmt.Println(int_32)

 fmt.Print("c=%c", ch3)

 var str string
 str = "i am bobby"
 fmt.Println(str)


} 

各種類型的間的轉換

 var str string
 str = "i am bobby"
 fmt.Println(str)
 // 字符串轉數字
 var istr = "12"
 myint, err := strconv.Atoi(istr)
 if err != nil {
  fmt.Println("convert error")
 }
 fmt.Println(myint)

 var myi = 32
 mstr := strconv.Itoa(myi)
 fmt.Println(mstr)

 // 字符串轉float32 轉換bool
 mFloat, error := strconv.ParseFloat("3.1415", 64)
 if error != nil {
  fmt.Println("convert error")
 }
 fmt.Println(mFloat)
 parseBol, err := strconv.ParseBool("true")
 if err != nil {
  fmt.Println("convert error")
 }
 fmt.Println(parseBol)
 // 基本類型轉字符串
 boolStr := strconv.FormatBool(true)
 fmt.Println(boolStr)

 floatStr := strconv.FormatFloat(3.1415, 'E', -1, 64)
 fmt.Println(floatStr)

運算符

go語言提供哪些集合類型

package main

import "fmt"

func main() {
 // 數組 slice map list
 // 數組 var name [count]int
 //var course1 [3]string // course1是類型 只有3個元素的數組類型
 //var course2 [4]string
 //course1[0] = "og"
 //course1[1] = "grpc"
 //course1[2] = "gin"
 ////[]strig 和 [3]string 這是兩種不同的類型
 //fmt.Println("%T \r\n", course1)
 //fmt.Println("%T \r\n", course2)
 //
 //for _, value := range course1 {
 // fmt.Printf("value=%s\n", value)
 //}

 // 初始化
 course1 := [3]string{"go", "grpc", "gin"}
 //course1 := [3]string{2:"gin"}
 // course3:=[...]string{"go","grpc","gin"}
 for _, value := range course1 {
  fmt.Printf("value=%s\n", value)
 }

 // 數組元素長度及內容一樣,可以直接用等於判斷
 // 多維數組
 var courseInfo [3][4]string
 courseInfo[0] = [4]string{"go", "1h", "bobby", "go體系課"}
 courseInfo[1] = [4]string{"grpc", "2h", "bobby1", "grpc入門"}
 courseInfo[2] = [4]string{"gin", "2h", "bobby2", "gin高級開發"}

 for i := 0; i < len(courseInfo); i++ {
  for j := 0; j < len(courseInfo[i]); j++ {
   fmt.Print(courseInfo[i][j] + "")
  }
  fmt.Println()
 }
}

切片

package main

import "fmt"

func main() {
 // 理解為動態的array 弱化數組的概念 切片的本質存儲和數組是有區別
 var courses []string
 fmt.Printf("%T \r\n", courses)

 // 這個方法很特別 添加元素
 courses = append(courses, "go")
 courses = append(courses, "grpc")
 courses = append(courses, "gin")

 fmt.Println(courses[1])

 // 初始化 3種 1:從數組直接創建 2:使用string{} 3:make
 allCourses := [5]string{"go", "grpc", "gin", "mysql", "elasticsearch"}
 courseSlice := allCourses[0:2] // 左閉右開的區間 python的語法
 fmt.Println(courseSlice)

 courseSlice1 := []string{"go", "grpc", "gin", "mysql", "elasticsearch"}
 fmt.Println(courseSlice1)
 courseSlice2 := make([]string, 3)
 courseSlice2[0] = "C"
 fmt.Println(courseSlice2)

 // 如何訪問切片的元素 訪問單個(類似數組,不能超長度) 訪問多個allCourses[start:end] 前閉後開,如果只有start(到結束)
 // 如果沒有start有end (從end之前的元素)
 // 沒有【:] 相當於複製了一份
 cSlice1 := []string{"go", "grpc"}
 cSlice2 := []string{"mysql", "es", "gin"}
 c12 := append(cSlice1, cSlice2[1:]...)
 fmt.Println(c12)
}
package main

import (
 "fmt"
 "strconv"
 "unsafe"
)

func printSlice(data []string) {
 data[0] = "java"
 for i := 0; i < 10; i++ {
  data = append(data, strconv.Itoa(i))
 }
}

// 定義slice會通過結構體去定義
type slice struct {
 array unsafe.Pointer
 len   int
 cap   int
}

func main() {
 //go的slice在函數 參數傳遞的時候是值傳遞還是引用傳遞,值傳遞,效果又呈現出引用的效果(不完全是)
 course := []string{"go", "grpc", "gin"}
 printSlice(course)
 fmt.Println(course)
}

defer 是有能力修改返回值的
error,panic,recover go語言的錯誤處理理念, 一個函數可能出錯,開發函數的人需要有一個返回值去告訴調用者是否成功,要求我們必須要處理這個error
go設計者認為必須要處理這個error,防禦型編輯

func A() (int,error){
  panic("this is an panic") // panic會導致程序的退出,平時開發中不要隨便使用,一般我人在哪裡用到, 我們一個服務啓動的過程中 

  return 0,errors.New("this is an error")
}

結構體

 package main

type Person struct {
 name    string
 age     int
 address string
 height  float32
}

type Person1 struct {
 name string
 age  int
}

type Student struct {
 //// 第一種嵌套方式
 //p     Person1
 //第二種方式 訪問的方式可以直接.name .age 但初始化時不能 類似覆蓋的方式
 Person1
 score float32
}

func main() {
 // 如何初始化結構體
 p1 := Person{"bobby1", 18, "七星高照", 1.80}
 p2 := Person{name: "bobby2", height: 1.90}
 var persons []Person
 persons = append(persons, p1)
 persons = append(persons, p2)
 persons = append(persons, Person{
  name: "bobby3",
 })
 // 匿名結構體
 address := struct {
  province string
  city     string
  address  string
 }{
  province: "北京市",
  city:     "通州區",
  address:  "萬壽路",
 }

 // 結構體的嵌套
 s := Student{
  p: Person1{
   name: "bob",
   age:  18,
  },
  score: 95.6,
 }
 s.p.name = "zzz"
}
// 結構體綁定方法
 // func(s StructType)funcName(param1 paramType,....)(returnTypes1....){.....}

指針

// 第一種初始化方式
ps := &Person{}

// 第二種初始化方式
var emptyPerson Person
pi := &emptyPerson

// 第三種初始化方式
var pp = new(Person)
fmt.Println(pp.name)

// 初始化兩個關鍵字,map、channel、slice 初始化推薦使用 make 方法
// 指針初始化推薦使用 new 函數,指針要初始化否則會出現 nil pointer
// map 必須初始化

指針傳遞交換兩個值

package main

import "fmt"

// 通過指針交換兩個值
func swap(a, b *int) {
 *a, *b = *b, *a
}

func main() {
 x, y := 10, 20

 fmt.Printf("交換前: x = %d, y = %d\n", x, y)

 // 調用 swap 函數交換值
 swap(&x, &y)

 fmt.Printf("交換後: x = %d, y = %d\n", x, y)
}

go語言中的nil

/*
不同類型的數據零值不一樣

bool       false
numbers    0
string     ""
pointer    nil
slice      nil
map        nil
channel、interface、function nil

struct 默認值不是 nil,默認值是具體字段的默認值
*/
package main

import "fmt"

type Person struct {
    name string
    age  int
}

func main() {
  // struct每一個值都等於才相當
    p1 := Person{
        name: "bobby",
        age:  18,
    }

    p2 := Person{
        name: "bobby",
        age:  18,
    }

    if p1 == p2 {
        fmt.Println("yes")
    }
}

go鴨子

// Go語言的接口,鴨子類型,php,python
// Go語言中處處都是interface,到處都是鴨子類型 duck typing

/*
當看到一隻鳥走起來像鴨子,游泳起來像鴨子,叫起來也像鴨子,那麼這只鳥就是鴨子
動詞,方法,具備某些方法
*/
// 主要是聲明方法的定義
type Duck interface {
  // 方法的申請
  Gaga()
  Walk()
  Swimming()
}
type pskDuck struct {
  legs int
}
func (pd *pskDuck) Gaga(){
  fmt.Println("嘎嘎")
}
func (pd *pskDuck) Walk(){
  fmt.Println("嘎嘎")
}
func (pd *pskDuck) Swimming (){
  fmt.Println("嘎嘎")
}

go語言的包組織

同一個文件夾下不允許出現不同名的pacage, 導入是使用路徑,使用時候是包名+類包,別名引用;.全部引入到當前包中使用, 初始化的時候會使用func init(){}, go.mod是自動維護的gin-gonic/gin
新版都是使用go modules go list -m all

go list -m --version 包名 可以查看版本
go get 指定包名
go mod tidy # go mod help 來查看
# go install 安裝
# go get -u 升級到最新的將要版本或修定版本
# # go get 會修改go.mod文件的
# go get github.com/go-redis/redis/v8@version 

代碼規範

1. 代碼規範

命名

  • 使用駝峰命名法,例如:myVariable
  • 包名應為小寫單詞,無需使用下划線或混合大小寫。
  • 接口命名以 -er 結尾,例如:ReaderWriter

格式化

  • 使用 gofmt 工具統一代碼格式。
  • 保持代碼風格一致,如縮進、空格和換行等。

注釋

  • 使用行注釋 // 為函數、方法和複雜邏輯添加說明。
  • 包級注釋應放在包聲明之前。

2. 結構體與接口

結構體

  • 盡量使用小寫字段名,除非需要導出。
  • 使用構造函數初始化結構體。

接口

  • 定義小而簡單的接口。
  • 使用接口滿足需要,而不是定義大而全的接口。

3. 錯誤處理

錯誤檢查

  • 始終檢查函數返回的錯誤。
  • 使用 errors.Newfmt.Errorf 創建錯誤信息。

錯誤處理

  • 錯誤應盡早處理,避免延遲檢查。
  • 當錯誤不可恢復時,使用 log.Fatal 記錄日誌並退出。
  • 盡量提供上下文信息以幫助調試。

為甚麼要代碼規範

  1. 代碼規範並不是強制的,但是不同的語言一些細微的規範還是要遵循的。
  2. 代碼規範主要是為方便團隊內形成一個統一的代碼風格,提高代碼的可讀性、統一性。

1. 代碼規範

1.1 命名規範

包名

  1. 盡量和目錄保持一致。
  2. 盡量採取有意義的包名,簡短。
  3. 不要和標準庫名衝突。
  4. 包名採用全部小寫。

文件名

  • 使用 user_name.go,如果有多個單詞可以採用蛇形命名法。

變量名

  1. 蛇形:適用於 python、php。
  2. 駝峰:適用於 java、c、go。
  3. userName
  4. UserName
  5. un string //unad userNameAndDesc
  6. 有一些專有命名,如 URLVersion
  7. bool 類型使用 Hasiscanallow 等前綴。

1.2 結構體命名

  • 使用駝峰,例如 User

1.3 接口命名

  • 接口命令基本上和結構體差不多。

單元測試

多線程

// python, java, php 多線程編程、多進程編程,多線程和多進程存在的問題主要是耗費內存
// 內存、線程切換、web2.0,用戶級線程,綠程,輕量級線程,協程,asyncio-python php-swoole java - netty
// 內存佔用小(2k)、切換快,go語言的協程,go語言誕生之後就只有協程可用 goroutine
// 主協程退出,在其中啓動的協程就會死掉
package main

import (
 "fmt"
 "time"
)

func asyncPrint() {
 fmt.Println("bobby")
}

func main() {
 // 主死隨從
 //go asyncPrint()
 // 1. 閉包 2. for循環的問題
 for i := 0; i < 100; i++ {
  go func(i int) {
   for {
    time.Sleep(time.Second)
    fmt.Println(i)
   }
  }(i)
 }

 fmt.Println("main goroutine")
 time.Sleep(2 * time.Second)
}

理解GMP

wg (wait group)

雖然我們可以使用主線程sleeep來保證主協程不死,但主不一定知道要sleep要多久,子goroutine如何通知到主的goroutine自己結束了,主的goroutine如何知道子的已經結束了

package main

import (
 "fmt"
 "sync"
)

func main() {
 var wg sync.WaitGroup

 // 我要監控多少個goroutine執行結束
 wg.Add(100)
 for i := 0; i < 100; i++ {
  go func(i int) {
   defer wg.Done()
   fmt.Println(i)
   //wg.Done() // 調用了要調用一次done
  }(i)
 }
 // 等到100都執行完
 wg.Wait()

 fmt.Println("all done")
 // waitgroup主要用於goroutine的執行等待 add方法要和done要和Done方法配套
}

goroutine如何使用鎖

package main

import (
 "fmt"
 "sync"
)

//鎖 資源競爭

var total int
var wg sync.WaitGroup
var lock sync.Mutex // 只要是同一把鎖就沒有問題

func add() {
 defer wg.Done()
 for i := 0; i < 100000; i++ {
  lock.Lock()
  total += 1
  lock.Unlock()
 }
}

func sub() {
 defer wg.Done()
 for i := 0; i < 100000; i++ {
  lock.Lock()
  total -= 1
  lock.Unlock()
 }
}

func main() {
 wg.Add(2)
 //兩個同時運行時,因為不是原子操作它會產生資源競爭,導致結果不一致
 go add()
 go sub()
 wg.Wait()

 fmt.Println("done i=", total)
}
// 如果權權是加一或者減一操作
// 可以使用atomic.AddInXX

讀寫鎖

上面我們已經學習了互斥(本質上是將並行的內容,串行化),使用lock肯定會影響性能
即使是設計鎖,那麼也要盡量保證並行
我們有兩組協程,一組寫,一組讀,web系統中絕大數是讀多寫少,比如詳情頁面
雖然有多個goroutine,但仔細分析我們會發現 讀協程之間應該是併發,讀和寫之間應該是串行

package main

import (
 "fmt"
 "sync"
 "time"
)

func main() {
 var num int
 var rwLocak sync.RWMutex
 var wg sync.WaitGroup

 wg.Add(2)

 go func() {
  defer wg.Done()
  // 負責寫數據
  rwLocak.Lock() // 加寫鎖 寫鎖會防止 別的寫鎖獲取和讀鎖獲取
  defer rwLocak.Unlock()
  num = 12
 }()

 // 因為不能保證 寫先執行(因為還沒講到goroutine之間的通信)我們先sleep一下吧
 time.Sleep(time.Second)
 // 讀取的goroutine
 go func() {
  defer wg.Done()
  rwLocak.RLock() // 加讀鎖 ,讀鎖不會阻止別人的讀
  defer rwLocak.RUnlock()
  fmt.Println(num)
 }()
 wg.Wait()
}

goroutine之間進行通信

package main

import "fmt"

func main() {
 /*
    不要通過共享內存來通信,而要通過通信來實現內存共享
    php python java 多線程編程的時候,丙從此goroutine之間通信最常用的方式是一個全局可以提供消息的機制
    python-queue java生產者消費者
    channel 在加上語法糖讓使用channel更加簡單
 */
 var msg chan string //可以理解是一個通道 還要定義通道的類型
 // 它底層是數組來完成,所以要初始一下
 msg = make(chan string, 1) // channel的初始化值為0時,你放的值會阻塞 deadlock
 // msg = make(chan string, 0)  // 無緩衝
 msg <- "bobby" // 放值 右邊的值放到這個channel中
 data := <-msg  // 拿值
 fmt.Println(data)
}

有緩衝和緩衝

  • 無緩衝 適用於通知 B要第一時間知道A是否已經完成
  • 有緩衝 適用於生產者之間通信
// 消息傳遞,消息過濾
// 信號廣播
// 事件訂閱和廣播
// 任務分發
// 結果匯總
// 併發控制
// 同步和異步
package main

import (
 "fmt"
 "time"
)

func main() {
 var msg chan int
 msg = make(chan int, 2)
 go func(msg chan int) {
  for data := range msg {
   fmt.Println(data)
  }
  fmt.Println("all done")
 }(msg)

 msg <- 1 // 放值channel中
 msg <- 2
 close(msg) // 關閉這個通道
 d := <-msg
 fmt.Println(d) // 已經關閉的channel可以繼續取值,但不能再放值了
 msg <- 3       // 已經關閉的channel不能再放值了

 time.Sleep(time.Second * 10)

}

單向channel

package main

import (
 "fmt"
 "time"
)

func producer(out chan<- int) {
 for i := 0; i < 10; i++ {
  out <- i * i
 }
 close(out)
}

func consumer(in <-chan int) {
 for num := range in {
  fmt.Printf("num=%d\r\n", num)
 }
}

func main() {
 // 默認情況下,channel是雙向的
 // 但是我們經常一個channel做為一個參數時,不希望往里寫數據
 // 單向channel
 //var ch1 chan int       // 雙向的
 //var ch2 chan<- float64 // 單向的只能寫入float64
 //var ch3 <-chan int     // 單向的,只能讀取數據

 //c := make(chan int, 3)
 //var send chan<- int = c // send-only
 //var read <-chan int = c // rec-oney
 //send <- 1
 //<-send //這樣就不行了,只能寫不能讀
 //<-read
 // 不能將單向的轉成普通的channel
 c := make(chan int)
 go producer(c)

 go consumer(c)

 time.Sleep(time.Second * 10)

}

一道常見面試題

package main

import (
 "fmt"
 "time"
)

var number, latter = make(chan bool), make(chan bool)

func printNum() {
 i := 1
 for {
  // 我怎麼去做到,應該此片,等待另一個goroutine來通知我
  <-number
  fmt.Printf("%d%d", i, i+1)
  i += 2
  latter <- true
 }
}

func printLetter() {
 i := 0
 str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 for {
  // 我怎麼去做到,應該此片,等待另一個goroutine來通知我
  <-latter
  if i >= len(str) {
   return
  }
  fmt.Print(str[i : i+2])
  i += 2
  number <- true
 }
}

func main() {
 /*
  使用兩個goroutine交替打印序列,一個goroutine打印數字,另外一個goroutine打印字母,最終效果如下:
  12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728
 */
 go printNum()
 go printLetter()
 number <- true
 time.Sleep(time.Second * 100)
}

監控

package main

import (
 "fmt"
 "sync"
 "time"
)

var done bool
var lock sync.Mutex

func go1() {
 time.Sleep(time.Second)
 lock.Lock()
 defer lock.Unlock()
 done = true
}

func go2() {
 time.Sleep(time.Second * 2)
 lock.Lock()
 defer lock.Unlock()
 done = true
}

func main() {
 // 類似於switch 但select的功能和我們操作系統linux裡面提供的io的select poll epoll類似
 // select 主要作用於多個channel
 // 現在有需求,我們現在有兩個goroutine都在執行,但是我們在主的goroutine中,當某一個執行完成了,這個時間我會立以知道
 go go1()
 go go2()
 for {
  time.Sleep(time.Millisecond * 10)
  if done {
   fmt.Println("done")
   return
  }
 }
}

更推崇消息的方式來通知,而不是共享變量的方式

context

併發編程中使用最多一個場景了

package main

import (
 "fmt"
 "sync"
 "time"
)

var wg sync.WaitGroup
var stop bool

// 我們新的需求,我可以主動退出監控程序
// 共享變量
func cupInfo() {
 defer wg.Done()
 for {
  if stop {
   break
  }
  time.Sleep(2 * time.Second)
  fmt.Println("CPU的信息 ")
 }
}

func main() {
 // 為甚麼使用context
 // 有一個goroutine監控cpu的信息
 wg.Add(1)
 go cupInfo()
 time.Sleep(6 * time.Second)
 stop = true
 wg.Wait()
 fmt.Println("監控完成")
}
// 演進
package main

import (
 "context"
 "fmt"
 "sync"
 "time"
)

var wg sync.WaitGroup

// var stop bool
//var stop = make(chan struct{})

// 我們新的需求,我可以主動退出監控程序
// 共享變量
func cupInfo(ctx context.Context) {
 defer wg.Done()
 for {
  select {
  case <-ctx.Done():
   fmt.Println("退出cpu監控")
   return
  default:
   time.Sleep(2 * time.Second)
   fmt.Println("CPU的信息 ")
  }

 }
}

func main() {
 // 為甚麼使用context
 // 有一個goroutine監控cpu的信息
 //var stop = make(chan struct{})
 wg.Add(1)
 ctx, cancel := context.WithCancel(context.Background())
 go cupInfo(ctx)
 time.Sleep(6 * time.Second)
 //stop <- struct{}{}
 cancel()
 wg.Wait()
 fmt.Println("監控完成")
}
package main

import (
 "context"
 "fmt"
 "sync"
 "time"
)

var wg sync.WaitGroup

// var stop bool
//var stop = make(chan struct{})

// 我們新的需求,我可以主動退出監控程序
// 共享變量
func cupInfo(ctx context.Context) {
 defer wg.Done()
 for {
  select {
  case <-ctx.Done():
   fmt.Println("退出cpu監控")
   return
  default:
   time.Sleep(2 * time.Second)
   fmt.Println("CPU的信息 ")
  }

 }
}

func main() {
 // 為甚麼使用context
 // 有一個goroutine監控cpu的信息
 //var stop = make(chan struct{})
 wg.Add(1)
 //ctx, cancel := context.WithCancel(context.Background())
 ctx, _ := context.WithTimeout(context.Background(), 6*time.Second)
 go cupInfo(ctx)
 //time.Sleep(6 * time.Second)
 //stop <- struct{}{}
 //cancel()
 wg.Wait()
 fmt.Println("監控完成")
}

主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/4768

(0)
Walker的頭像Walker
上一篇 2025年11月25日 01:00
下一篇 2025年11月24日 02:00

相關推薦

  • Go工程師體系課 008【學習筆記】

    訂單及購物車 先從庫存服務中將 srv 的服務代碼框架複製過來,查找替換對應的名稱(order_srv) 加密技術基礎 對稱加密(Symmetric Encryption) 原理: 使用同一個密鑰進行加密和解密 就像一把鑰匙,既能鎖門也能開門 加密速度快,適合大量數據傳輸 使用場景: 本地文件加密 數據庫內容加密 大量數據傳輸時的內容加密 內部系統間的快速通…

    個人 2025年11月25日
    19400
  • 深入理解ES6 001【學習筆記】

    塊級作用域綁定 之前的變量聲明var無論在哪裡聲明的都被認為是作域頂部聲明的,由於函數是一等公民,所以順序一般是function 函數名()、var 變量。 塊級聲明 塊級聲明用於聲明在指定塊的作用域之外無法訪問的變量。塊級作用域存在於: 函數內部 塊中(字符和{和}之間的區域) 臨時死區 javascript引擎在掃描代碼發現變量聲明時,要麼將它們提升至作…

    個人 2025年3月8日
    1.6K00
  • 深入理解ES6 005【學習筆記】

    解構:使用數據訪問更便捷 如果使用var、let或const解構聲明變量,則必須要提供初始化程序(也就是等號右側的值)如下會導致錯誤 // 語法錯誤 var {tyep,name} // 語法錯誤 let {type,name} // 語法錯誤 const {type,name} 使用解構給已經聲明的變量賦值,哪下 let node = { type:&qu…

    個人 2025年3月8日
    1.2K00
  • Go工程師體系課 007【學習筆記】

    商品微服務 實體結構說明 本模塊包含以下核心實體: 商品(Goods) 商品分類(Category) 品牌(Brands) 輪播圖(Banner) 品牌分類(GoodsCategoryBrand) 1. 商品(Goods) 描述平台中實際展示和銷售的商品信息。 字段說明 字段名 類型 說明 name String 商品名稱,必填 brand Pointer …

    個人 2025年11月25日
    21100
  • Go工程師體系課 018【學習筆記】

    API 網關與持續部署入門(Kong & Jenkins) 對應資料目錄《第 2 章 Jenkins 入門》《第 3 章 通過 Jenkins 部署服務》,整理 Kong 與 Jenkins 在企業級持續交付中的實戰路徑。即便零基礎,也能順著步驟搭建出自己的網關 + 持續部署流水線。 課前導覽:甚麼是 API 網關 API 網關位於客戶端與後端微服務…

    個人 2025年11月25日
    18700
簡體中文 繁體中文 English
歡迎🌹 Coding never stops, keep learning! 💡💻 光臨🌹