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/6733

(0)
Walker的頭像Walker
上一篇 13小時前
下一篇 1天前

相關推薦

  • Go工程師體系課 016

    Kubernetes 入門 —— Go 微服務部署與編排 一、Kubernetes 核心概念 1.1 什麼是 Kubernetes Kubernetes(簡稱 K8s)是 Google 開源的容器編排平臺,用於自動化部署、擴展和管理容器化應用。如果說 Docker 解決了"如何打包和運行單個容器"的問題,那麼 K8s 解決的是"如何管理成百上千個容器"的問題…

    後端開發 30分鐘前
    000
  • 編程基礎 0008_標準庫進階

    Go 標準庫進階 系統整理 Go 標準庫中最常用的包,重點覆蓋 io、os、bufio、strings、time、fmt 等 1. io 包核心接口 Go 的 I/O 設計圍繞幾個核心接口展開,幾乎所有 I/O 操作都基於它們。 // 最基礎的兩個接口 type Reader interface { Read(p []byte) (n int, err er…

    後端開發 19小時前
    500
  • Go工程師體系課 010

    es 安裝 elasticsearch(理解爲庫) kibana(理解爲連接工具)es 和 kibana(5601) 的版本要保持一致 MySQL 對照學習 Elasticsearch(ES) 術語對照 MySQL Elasticsearch database index(索引) table type(7.x 起固定爲 _doc,8.x 徹底移除多 type…

    後端開發 6小時前
    100
  • Go資深工程師講解(慕課) 000_課程目錄索引

    Google資深工程師深度講解Go語言 - 課程目錄索引 課程來源:慕課網(百度網盤備份)講師風格:從 Google 工程實踐出發,注重底層原理和工程規範 完整視頻章節與筆記對照表 章節 視頻文件 筆記位置 狀態 Ch1 課程介紹 1-1 課程導讀 — 跳過 1-2 安裝與環境 001.md > GOPATH、環境變量 已覆蓋 Ch2 基礎語法 2-1…

    後端開發 22小時前
    100
  • Go工程師體系課 015

    Docker 容器化 —— Go 項目實戰指南 一、Docker 核心概念 1.1 什麼是 Docker Docker 是一個開源的容器化平臺,它可以將應用程序及其所有依賴項打包到一個標準化的單元(容器)中,從而實現"一次構建,到處運行"。對於 Go 開發者而言,Docker 解決了以下痛點: 開發環境與生產環境不一致 依賴管理複雜(數據庫、緩存、消息隊列等…

簡體中文 繁體中文 English