圖片制作網頁關于進一步優(yōu)化落實疫情防控措施
目錄
- 泛型
- 內置泛型的使用
- 切片泛型和泛型函數(shù)
- map泛型
- 泛型約束
- 泛型完整代碼
- 接口
- 反射
- 協(xié)程
- 特點
- WaitGroup
- goroutine的調度模型:MPG模型
- channel
- 介紹
- 語法:
- 舉例:
- channel遍歷
- 基本使用
- 和協(xié)程一起使用
- 案例一
- 案例二
- select...case
- main.go 完整代碼
- 文件
go往期文章筆記:
【Java轉Go】快速上手學習筆記(一)之環(huán)境安裝篇
【Java轉Go】快速上手學習筆記(二)之基礎篇一
【Java轉Go】快速上手學習筆記(三)之基礎篇二
這篇主要是泛型、接口、反射、協(xié)程、管道、文件操作的筆記,因為我的筆記都是按照視頻說的來記的,可能大家光看的話會有些看不明白,所以建議大家可以把代碼復制下來,配合里面的注釋一起,自己去運行一遍,加深理解😘
泛型
定義泛型:func 函數(shù)名 [泛型參數(shù)類型] (函數(shù)參數(shù)) {}
內置泛型的使用
Go內置的兩個泛型:any 和 comparable
- any:表示go里面所有的內置基本類型,等價于
interface{}
- comparable:表示go里面所有內置的可比較類型:
int、uint、float、booi.struct、指針
等一切可以比較的類型
func printArr[T any](arr []T) {for _, item := range arr {fmt.Println(item)}
}// 泛型的使用
func 泛型的基本使用() {strArr := []string{"白夜", "熾翎"}intArr := []int{1, 2}printArr(strArr)printArr(intArr)
}
切片泛型和泛型函數(shù)
// 自定義一個切片泛型
type mySlice[T int | float64] []T// 給自定義的切片泛型添加一個求和方法
func (s mySlice[T]) sum() T {var sum Tfor _, v := range s {sum += v}return sum
}// 定義一個泛型函數(shù)
func add[T int | float64 | float32 | string](a T, b T) T {return a + b
}// 泛型函數(shù)與方法
func 切片泛型的使用() {// 往自定義的切片泛型里,添加int類型的值var i mySlice[int] = []int{1, 2, 3, 4}fmt.Println(i.sum()) // 可以直接調用為切片泛型添加的一個求和方法// 往自定義的切片泛型里,添加float64類型的值var f mySlice[float64] = []float64{1.5, 2.7, 3.89, 4.55}fmt.Println(f.sum())//fmt.Println(add[int](1, 2))fmt.Println(add(1, 2)) // 調用時,可以自動推導傳入的參數(shù)的類型//fmt.Println(add[string]("hh", "66"))fmt.Println(add("hh", "66"))//fmt.Println(add[float64](1.6, 2.8))fmt.Println(add(1.6, 2.8))
}
map泛型
// 泛型map
type myMap[K string | int, V any] map[K]V
type User struct {Name string
}func map泛型的使用() {m1 := myMap[string, string]{"key": "符華",}fmt.Println(m1)m2 := myMap[int, User]{0: User{"符華"},}fmt.Println(m2)
}
泛型約束
// 自定義一個類型別名(將int8類型設置一個別名)
type int8A int8// 自定義一個泛型約束
type myInt interface {// 當類型設置了別名,在使用的時候要么在后面把這個別名約束也加進去//int | int8 | int16 | int32 | int64 | int8A// 要么在這個類型前面,加一個 ~ 符號,因為類型的別名是這個類型的衍生類型,在類型前面加 ~ 號就可以適配這個類型的全部衍生類型了int | ~int8 | int16 | int32 | int64
}// 給泛型約束定義一個比較大小的泛型函數(shù)
func getMaxNum[T myInt](a, b T) T {if a > b {return a}return b
}func 泛型約束的使用() {//var a int = 10var a int8A = 10//var b int = 20var b int8A = 20fmt.Println(getMaxNum(a, b))
}
泛型完整代碼
package mainimport "fmt"/*
泛型:內置的泛型類型 any 和 comparable
any:表示go里面所有的內置基本類型,等價于 interface{}
comparable:表示go里面所有內置的可比較類型:`int、uint、float、booi.struct、指針` 等一切可以比較的類型
*/
func printArr[T any](arr []T) {for _, item := range arr {fmt.Println(item)}
}// 自定義一個切片泛型
type mySlice[T int | float64] []T// 給自定義的切片泛型添加一個求和方法
func (s mySlice[T]) sum() T {var sum Tfor _, v := range s {sum += v}return sum
}// 泛型函數(shù)
func add[T int | float64 | float32 | string](a T, b T) T {return a + b
}// 泛型map
type myMap[K string | int, V any] map[K]V
type User struct {Name string
}// 自定義一個類型別名(將int8類型設置一個別名)
type int8A int8// 自定義一個泛型約束
type myInt interface {// 當類型設置了別名,在使用的時候要么在后面把這個別名約束也加進去//int | int8 | int16 | int32 | int64 | int8A// 要么在這個類型前面,加一個 ~ 符號,因為類型的別名是這個類型的衍生類型,在類型前面加 ~ 號就可以適配這個類型的全部衍生類型了int | ~int8 | int16 | int32 | int64
}// 給泛型約束定義一個比較大小的泛型函數(shù)
func getMaxNum[T myInt](a, b T) T {if a > b {return a}return b
}func main() {//泛型的基本使用()//切片泛型的使用()//map泛型的使用()泛型約束的使用()
}// 泛型的使用
func 泛型的基本使用() {strArr := []string{"白夜", "熾翎"}intArr := []int{1, 2}printArr(strArr)printArr(intArr)
}// 泛型函數(shù)與方法
func 切片泛型的使用() {// 往自定義的切片泛型里,添加int類型的值var i mySlice[int] = []int{1, 2, 3, 4}fmt.Println(i.sum()) // 可以直接調用為切片泛型添加的一個求和方法// 往自定義的切片泛型里,添加float64類型的值var f mySlice[float64] = []float64{1.5, 2.7, 3.89, 4.55}fmt.Println(f.sum())//fmt.Println(add[int](1, 2))fmt.Println(add(1, 2)) // 調用時,可以自動推導傳入的參數(shù)的類型//fmt.Println(add[string]("hh", "66"))fmt.Println(add("hh", "66"))//fmt.Println(add[float64](1.6, 2.8))fmt.Println(add(1.6, 2.8))
}func map泛型的使用() {m1 := myMap[string, string]{"key": "符華",}fmt.Println(m1)m2 := myMap[int, User]{0: User{"符華"},}fmt.Println(m2)
}func 泛型約束的使用() {//var a int = 10var a int8A = 10//var b int = 20var b int8A = 20fmt.Println(getMaxNum(a, b))
}
接口
接口:用 type 和 interface 關鍵字定義
定義格式:
type 接口名 interface {接口方法1(參數(shù)1 參數(shù)類型.....) [返回類型]接口方法2() [返回類型]接口方法3()...接口方法n() [返回類型]
}
接口可以將不同的類型綁定到一組公共的方法上,從而實現(xiàn)多態(tài)。(提高代碼的復用率)
Go中的接口是隱式實現(xiàn)的,也就是說,如果一個類型實現(xiàn)了一個接口定義的所有方法,那么它就自動地實現(xiàn)了該接口。(不用像Java一樣,用implements關鍵字指定實現(xiàn)哪個接口)
因此,我們可以通過將接口作為參數(shù)來實現(xiàn)對不同類型的調用,從而實現(xiàn)多態(tài)。
// 定義一個 寸勁 接口
type 寸勁 interface {// 這個接口里面有這幾個方法寸勁開天(days int) string // 有參數(shù),有返回值的方法寸勁山崩() string // 無參數(shù),有返回值的方法寸勁巖破() // 無參數(shù),無返回值的方法
}// 定義一個 太虛劍氣 接口
type 太虛劍氣 interface {太虛劍神(days int) string
}// 定義一個函數(shù),以空接口作為參數(shù)(可以傳任何類型的參數(shù))
func dataPrint(datas ...interface{}) {for i, x := range datas {switch x.(type) {case bool:fmt.Printf("參數(shù) #%d 是一個bool類型,它的值是:%v\n", i, x)case string:fmt.Printf("參數(shù) #%d 是一個string類型,它的值是:%v\n", i, x)case int:fmt.Printf("參數(shù) #%d 是一個int類型,它的值是:%v\n", i, x)case float64:fmt.Printf("參數(shù) #%d 是一個float64類型,它的值是:%v\n", i, x)case nil:fmt.Printf("參數(shù) #%d 是一個nil類型,它的值是:%v\n", i, x)default:fmt.Printf("參數(shù) #%d 是其他類型,它的值是:%v\n", i, x)}}
}// 定義一個用戶學習結構體,來實現(xiàn)接口所有個方法(一個類型實現(xiàn)了接口的所有方法,即實現(xiàn)了該接口)
type 學習 struct {name string
}// 定義一個結構體特有的方法
func (x 學習) 開始學習() string {return fmt.Sprint(x.name, "現(xiàn)在要開始學習了.....")
}// 實現(xiàn) 寸勁開天 接口(這里也可以用指針 x *學習,用了指針后,那么賦值的時候也需要傳指針類型:&學習{"符華"})
func (x *學習) 寸勁開天(days int) string {return fmt.Sprint(x.name, "學了", days, "天,學完了寸勁開天")
}// 實現(xiàn) 寸勁山崩 接口
func (x 學習) 寸勁山崩() string {return fmt.Sprint(x.name, "學完了寸勁山崩")
}// 實現(xiàn) 寸勁巖破 接口
func (x 學習) 寸勁巖破() {fmt.Println(x.name, "學完了寸勁巖破")
}// 實現(xiàn) 太虛劍神 接口
func (x *學習) 太虛劍神(days int) string {return fmt.Sprint(x.name, "學了", days, "天,學完了太虛劍神")
}func main() {接口的使用()
}func 接口的使用() {u := 學習{"符華"}var cj 寸勁//cj = u // 接口賦值為 學習 結構體,只有當實現(xiàn)了接口的全部方法才能賦值給接口,否則無法賦值cj = &u // 只要接口方法有一個指針實現(xiàn),則此處必須是指針if u1, ok := cj.(*學習); ok { // 通過類型斷言,來調用 結構體 獨有的方法fmt.Println(u1.開始學習())}cj.寸勁巖破()fmt.Println(cj.寸勁山崩())fmt.Println(cj.寸勁開天(2))/*類型斷言:由于接口是一般類型,不知道具體類型,如果要轉成具體類型,就需要使用類型斷言語法:接口.(類型),類型不是什么類型都可以傳,必須要 接口 原先指向什么類型,那么就傳什么類型返回兩個值,可以通過返回的 true、false 來判斷斷言(轉換)是否成功*///var jq 太虛劍氣//jq, ok := cj.(太虛劍氣)if jq, ok := cj.(太虛劍氣); ok { // 如果轉換成功,ok為truefmt.Println(jq.太虛劍神(10))} else {fmt.Println("轉換失敗")}var a interface{}a = u // 將 u 賦值給a,然后將 a 重新賦值給一個 學習 類型的變量,這就需要用 類型斷言var u1 學習//u1 = a // 這里不可以直接賦值,需要使用類型斷言u1 = a.(學習) // a 原先指向 學習 類型,所以傳類型時也必須要傳 學習 類型fmt.Println(u1)// 空接口dataPrint(u, "空接口", 123, 12.65, []int{1, 2, 3}, make(map[string]string, 2))
}
反射
Go中,使用反射需要導入 reflect
包
使用反射時,主要有兩個很重要的方法:
reflect.TypeOf(變量名)
,獲取變量的類型,返回reflect.Type
類型(是一個接口)reflect.ValueOf(變量名)
,獲取變量的值,返回reflect.Value
類型(是一個結構體類型)
變量、interface{} 和 reflect.Value 是可以相互轉換的,如下圖:
package mainimport ("fmt""reflect"
)/*
反射:需要導入 reflect 包
主要有兩個函數(shù):reflect.TypeOf(變量名),獲取變量的類型,返回 reflect.Type 類型(是一個接口)reflect.ValueOf(變量名),獲取變量的值,返回 reflect.Value 類型(是一個結構體類型)變量、interface{}和 reflect.Value 是可以相互轉換的
*/type student struct {Name string `json:"name"`Age int
}// 給 student 結構體綁定方法
func (s student) PrintStu() {fmt.Println(s)
}
func (s student) GetSum(a, b int) {fmt.Println(a + b)
}// 基本數(shù)據(jù)類型、interface{}、reflect.Value 相互轉換
func reflectTest01(a interface{}) {// 通過反射獲取傳入的變量的 typerTyp := reflect.TypeOf(a)fmt.Println("rTyp=", rTyp)// 獲取到 reflect.ValuerVal := reflect.ValueOf(a)n1 := 10 + rVal.Int() // 通過反射來獲取變量的值,要求數(shù)據(jù)類型匹配:reflect.Value.Int()、reflect.Value.String()、reflect.Value.Float()......fmt.Println(n1)fmt.Printf("rVal=%v , rVal的類型=%T\n", rVal, rVal)// 將 reflect.Value 轉回 interface{}iV := rVal.Interface()// 將 interface{} 通過斷言轉回 需要的類型n2 := iV.(int)fmt.Println(n2)
}// 對結構體的反射
func reflectTest02(a interface{}) {// 通過反射獲取傳入的變量的 typerTyp := reflect.TypeOf(a)fmt.Println("rTyp=", rTyp)// 獲取到 reflect.ValuerVal := reflect.ValueOf(a)fmt.Printf("rVal=%v , rVal的類型=%T\n", rVal, rVal)fmt.Println("kind=", rVal.Kind(), rTyp.Kind())// 將 reflect.Value 轉回 interface{}iV := rVal.Interface()// 通過反射來獲取結構體的值,需要先斷言// 將 interface{} 通過斷言轉回 需要的類型stu := iV.(student)fmt.Println(stu)
}// 通過反射改變值(必須傳入指針,才能改變值)
func reflectTest03(a interface{}) {rTyp := reflect.TypeOf(a) // 通過反射獲取傳入的變量的 typefmt.Println("rTyp=", rTyp)rVal := reflect.ValueOf(a) // 獲取到 reflect.Valueswitch a.(type) { // 判斷傳入的參數(shù)的類型case *int:n1 := 10 + rVal.Elem().Int() // 通過反射來獲取變量的值,因為傳入的是指針,所以要先用 Elem() 再獲取值fmt.Println(n1)fmt.Printf("rVal=%v , rVal的類型=%T\n", rVal.Elem(), rVal)rVal.Elem().SetInt(200) // 通過反射改變值case *student:e := rVal.Elem()e.FieldByName("Name").SetString("白夜") // 給指定的字段名改變值}
}// 通過反射遍歷結構體的方法和屬性
func reflectTest04(a interface{}) {rTyp := reflect.TypeOf(a)if rTyp.Kind() != reflect.Struct { // 判斷傳入的參數(shù)是否是結構體return}rVal := reflect.ValueOf(a)// 遍歷結構體字段numField := rTyp.NumField() // 獲取結構體字段的數(shù)量fmt.Println("numField =", numField)for i := 0; i < numField; i++ {// 打印字段的類型、字段名、字段值、字段標簽fmt.Println(rTyp.Field(i).Type, rTyp.Field(i).Name, "=", rVal.Field(i), rTyp.Field(i).Tag.Get("json"))}// 遍歷結構體方法numMethod := rTyp.NumMethod() // 獲取結構體方法的數(shù)量// 關于方法遍歷時,方法的索引:是根據(jù)方法名稱的ACSII碼來排序的for i := 0; i < numMethod; i++ {// 打印方法的類型、方法名//fmt.Println(rTyp.Method(i).Type, rTyp.Method(i).Name)if i == 0 {var params []reflect.Valueparams = append(params, reflect.ValueOf(10))params = append(params, reflect.ValueOf(20))rVal.Method(i).Call(params)} else {rVal.Method(i).Call(nil)}}
}func main() {// 基本數(shù)據(jù)類型、interface{}、reflect.Value 相互轉換//var num int = 100//reflectTest01(num)//reflectTest03(&num) // 修改值必須傳指針//fmt.Println("通過反射改變num的值", num)stu := student{"符華", 20}//reflectTest02(stu)//reflectTest03(&stu) // 修改值必須傳指針//fmt.Println("通過反射改變stu的值", stu)reflectTest04(stu)
}
協(xié)程
接下來我們講協(xié)程
協(xié)程:一個進程有多個線程,一個線程可以起多個協(xié)程
特點
- 有獨立的棧空間
- 共享程序堆空間
- 調度由用戶控制
- 協(xié)程是輕量級的線程
主線程結束后,協(xié)程會被中斷,這時需要一個有效的阻塞機制。
WaitGroup
如果主線程退出了,即使協(xié)程還沒有執(zhí)行完畢,也會退出。這時,我們可以使用WaitGroup,它用于等待一組協(xié)程的結束。
- 父線程調用Add方法來設定應等待的協(xié)程的數(shù)量。
- 每個被等待的協(xié)程在結束時應調用Done方法。
- 同時,主線程里可以調用Wait方法阻塞至所有協(xié)程結束。
goroutine的調度模型:MPG模型
- M:操作系統(tǒng)的主線程(是物理線程)
- P:協(xié)程執(zhí)行需要的上下文
- G:協(xié)程
使用 goroutine 效率高,但是會出現(xiàn)并發(fā)/并行安全問題,需要加鎖解決這個問題。
如果協(xié)程發(fā)生異常,可以用recover來捕獲異常,進行除了。這樣主函數(shù)不會受到影響,可以繼續(xù)執(zhí)行。
package mainimport ("fmt""strconv""sync""time"
)// 一個函數(shù),每隔1秒輸出
func goroutineTest01() {for i := 0; i < 10; i++ {fmt.Println("test() hello,world " + strconv.Itoa(i))time.Sleep(time.Second)}
}var (myMap = make(map[int]int, 10)// 定義一個全局的互斥鎖lock sync.Mutex // sync 同步的意思,Mutex 互斥的意思wg sync.WaitGroup // 用于等待一組線程的結束
)func goroutineTest02(n int) {res := 1for i := 1; i <= n; i++ {res *= i}lock.Lock() // 寫之前加鎖myMap[n] = res // concurrent map writes 并發(fā)寫入問題lock.Unlock() // 寫完之后解鎖//wg.Add()中有20個需要執(zhí)行的協(xié)程,每執(zhí)行完一個后調用wg.Done(),讓協(xié)程數(shù)量-1,直到協(xié)程數(shù)量為0,表示全部協(xié)程執(zhí)行完畢wg.Done() // 這里表示每執(zhí)行完一個協(xié)程,wg.Add()里面的數(shù)量-1
}func main() {協(xié)程()
}func 協(xié)程() {//goroutineTest01() // 如果這樣調用,這里是先執(zhí)行完goroutineTest01,再執(zhí)行main里面的打印//go goroutineTest01() // 開啟了一個線程,這樣goroutineTest01和main就是同時執(zhí)行//for i := 0; i < 10; i++ {// fmt.Println("main() hello,world " + strconv.Itoa(i))// time.Sleep(time.Second)//}//cpuNum := runtime.NumCPU() //獲取電腦的cpu數(shù)量//fmt.Println("cpu個數(shù):", cpuNum)// 可以自己設置使用多少個cpu//runtime.GOMAXPROCS(cpuNum - 1) // 預留一個cpuwg.Add(20) // 這里表示有20個協(xié)程需要執(zhí)行// 開啟多個協(xié)程for i := 1; i <= 20; i++ {go goroutineTest02(i)}wg.Wait() // 告訴主線程要等一下,等協(xié)程全部執(zhí)行完了載退出fmt.Println("全部協(xié)程執(zhí)行完畢")// 遍歷輸出map結果for i, v := range myMap {fmt.Printf("map[%d]=%d\n", i, v)}
}func 協(xié)程異常捕獲() {// 這里我們可以使用defer + recover來捕獲異常defer func() {if err := recover(); err != nil {fmt.Println("發(fā)生錯誤,錯誤信息:", err)}}()var myMap map[int]stringmyMap[0] = "Go" // map沒有make,出現(xiàn)error
}
channel
channel也就是管道,一般情況下,我們是配合協(xié)程一起使用的。
channel管道:本質就是一個數(shù)據(jù)結構——隊列
介紹
- 數(shù)據(jù)是先進先出:FIFO:first in first out
- 線程安全,多goroutine訪問時,不需要加鎖,就是說channel本身就是線程安全的
- channel是有類型的,一個string的channel只能存放string類型數(shù)據(jù)
- channel必須是引用類型,必須初始化才能寫入數(shù)據(jù),也就是需要make后才能使用
語法:
var 變量名 chan 數(shù)據(jù)類型
,變量名 = make(chan 數(shù)據(jù)類型, 容量)
(使用make進行初始化)
舉例:
var intChan chan int // 用于存放int數(shù)據(jù)
var mapChan chan map[int]string // 用于存放map[int]string數(shù)據(jù)
var perChan chan Pserson // 用于存放Pserson結構體數(shù)據(jù)
var perChan1 chan *Pserson //用于存放Pserson結構體指針數(shù)據(jù)
var perChan1 chan interface{} //可以存放任何類型數(shù)據(jù),但是取的時候要注意用類型斷言
channel遍歷
-
通常使用for-range方式進行遍歷,不用取長度的方式來遍歷管道是因為管道每取一次,長度就會變。
-
在遍歷時,如果管道沒有關閉,會出現(xiàn)deadlock的錯誤;如果管道已經關閉,則正常遍歷數(shù)據(jù),遍歷完后,退出遍歷。
管道可以聲明為只讀或只寫(默認情況下是雙向的,也就是可讀可寫)
- 只寫:
var intChan chan<- int
- 只讀:
var intChan <-chan int
基本使用
func 管道() {// 創(chuàng)建一個可以存放3個int類型的管道var intChan chan int// 因為channel是引用類型,它的值其實是一個地址,然后這個地址指向的就是管道隊列;然后intChan本身也有一個地址intChan = make(chan int, 3)fmt.Printf("intChan 的值=%v intChan 本身的地址=%p\n", intChan, &intChan) // intChan 的值=0xc00006e080 intChan 本身的地址=0xc00004c020// 向管道寫入數(shù)據(jù),寫入、讀取管道數(shù)據(jù)時,用 <- 表達式intChan <- 10num := 200intChan <- num// 設置的管道容量是3,最多只能往里面寫入3條數(shù)據(jù)(長度不能超過容量)intChan <- 100// 管道的長度和cap(容量)fmt.Printf("管道 長度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 3,3// 讀取管道的數(shù)據(jù)。從管道中取出了數(shù)據(jù),可以再往里面放數(shù)據(jù)//<-intChan // 可以直接這么寫,也是取出數(shù)據(jù);不用變量接收,把取出的數(shù)據(jù)扔了不要n1 := <-intChan // 這里取出來的是最先寫入到管道里的數(shù)據(jù)(先進先出)fmt.Println("n1=", n1) // 10fmt.Printf("管道 長度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 2,3// 取出了一條數(shù)據(jù)后再往里面放一條數(shù)據(jù)intChan <- 500close(intChan) // 關閉管道,這時就不能再往管道里面寫入數(shù)據(jù)了,但是讀取沒問題fmt.Printf("管道 長度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 3,3// 在沒有使用協(xié)程的情況下,如果管道數(shù)據(jù)已經全部取出,再取會報錯n2 := <-intChann3 := <-intChanfmt.Printf("n2 = %v n3 = %v\n", n2, n3) // 200,100fmt.Printf("管道 長度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 1,3// 遍歷管道intChan2 := make(chan int, 100)for i := 0; i < 100; i++ {intChan2 <- i * 2}close(intChan2) // 管道寫完數(shù)據(jù)后,先將管道關閉,再進行遍歷// 不能用取長度的方式來遍歷管道,因為管道每取一次,長度就會變,要用 range 方式遍歷for v := range intChan2 { // 這里只返回一個數(shù)據(jù),管道里面沒有下標fmt.Println("v =", v)}
}
和協(xié)程一起使用
案例一
package mainimport ("fmt""sync"
)// 全局 WaitGroup 變量
var wg sync.WaitGroup // 用于等待一組線程的結束// 管道寫入數(shù)據(jù)
func writeData(intChan chan int) {for i := 0; i < 50; i++ {intChan <- ifmt.Printf("writeData 寫入數(shù)據(jù)=%v\n", i)}close(intChan)wg.Done() // 執(zhí)行完一個線程后,調用這個方法,主線程中需要等待執(zhí)行的協(xié)程數(shù)量-1
}// 管道讀取數(shù)據(jù)
func readData(intChan chan int) {for {v, ok := <-intChanif !ok {break}fmt.Printf("readData 讀到數(shù)據(jù)=%v\n", v)}wg.Done() // 執(zhí)行完一個線程后,調用這個方法,主線程中需要等待執(zhí)行的協(xié)程數(shù)量-1
}func main() {協(xié)程和管道應用1()
}func 協(xié)程和管道應用1() {// 創(chuàng)建兩個管道intChan := make(chan int, 10)wg.Add(2) // 說明開啟了兩個線程// 開啟了兩個協(xié)程,writeData和readData應該是交叉執(zhí)行的go writeData(intChan) // 開啟一個協(xié)程,往 intChan 中寫入數(shù)據(jù)go readData(intChan) // 開啟一個協(xié)程,讀取 intChan 的數(shù)據(jù)wg.Wait() // 告訴主線程需要等待協(xié)程執(zhí)行完畢fmt.Println("程序執(zhí)行完畢!")
}
案例二
-
需求:要求統(tǒng)計1-8000的數(shù)字中,哪些是素數(shù)
-
將統(tǒng)計素數(shù)的任務,分配給4個協(xié)程去完成
// 判斷是否為素數(shù)
func isPrime(intChan, primeChan chan int) {var isPrime bool // 標識是否是素數(shù)for v := range intChan {isPrime = truefor i := 2; i < v; i++ {if v%i == 0 {isPrime = falsebreak}}if isPrime { // 如果為素數(shù),則往primeChan中寫入數(shù)據(jù)primeChan <- v}}fmt.Println("isPrime 讀取素數(shù)完畢")wg.Done() // 執(zhí)行完一個線程后,調用這個方法,主線程中需要等待執(zhí)行的協(xié)程數(shù)量-1
}func 協(xié)程和管道應用2() {// 需求:要求統(tǒng)計1-8000的數(shù)字中,哪些是素數(shù)// 將統(tǒng)計素數(shù)的任務,分配給4個協(xié)程去完成intChan := make(chan int, 1000) // 讀寫1-8000數(shù)字的管道primeChan := make(chan int, 2000) // 存儲素數(shù)的管道wg.Add(5) // 下面開啟了5個協(xié)程// 開啟寫入 1-8000 數(shù)字的協(xié)程go func() {for i := 1; i <= 8000; i++ {intChan <- i}close(intChan)wg.Done()}()// 開啟4個讀取 1-8000 數(shù)字,并統(tǒng)計素數(shù)的協(xié)程for i := 0; i < 4; i++ {go isPrime(intChan, primeChan)}wg.Wait() // 等待協(xié)程執(zhí)行完畢close(primeChan) // 關閉 primeChan 管道// 遍歷primeChan,把結果取出來for v := range primeChan {fmt.Printf("素數(shù)是 = %v\n", v)}
}
select…case
傳統(tǒng)的方法在遍歷管道時,如果不關閉會阻塞而導致 deadlock。
在實際開發(fā)中,可能不好確定什么時候關閉管道,這時可以使用select方式解決。
func 管道注意細節(jié)() {intChan := make(chan int, 10)for i := 0; i < 10; i++ {intChan <- i}stringChan := make(chan string, 5)for i := 0; i < 5; i++ {stringChan <- "hello" + fmt.Sprintf("%d", i)}// 傳統(tǒng)的方法在遍歷管道時,如果不關閉會阻塞而導致 deadlock// 在實際開發(fā)中,可能不好確定什么時候關閉管道,這時可以使用select方式解決for {select {// 這里如果intChan一直沒有關閉,也不會一直阻塞而導致deadlock// 如果一個case取不到數(shù)據(jù),會自動到下一個case中取case v := <-intChan:fmt.Println("從intChan讀取的數(shù)據(jù)=", v)case v := <-stringChan:fmt.Println("從stringChan讀取的數(shù)據(jù)=", v)default:fmt.Println("都取不到了")return}}
}
main.go 完整代碼
package mainimport ("fmt""sync"
)var wg sync.WaitGroup // 用于等待一組線程的結束// 管道寫入數(shù)據(jù)
func writeData(intChan chan int) {for i := 0; i < 50; i++ {intChan <- ifmt.Printf("writeData 寫入數(shù)據(jù)=%v\n", i)}close(intChan)wg.Done() // 執(zhí)行完一個線程后,調用這個方法,主線程中需要等待執(zhí)行的協(xié)程數(shù)量-1
}// 管道讀取數(shù)據(jù)
func readData(intChan chan int) {for {v, ok := <-intChanif !ok {break}fmt.Printf("readData 讀到數(shù)據(jù)=%v\n", v)}wg.Done() // 執(zhí)行完一個線程后,調用這個方法,主線程中需要等待執(zhí)行的協(xié)程數(shù)量-1
}// 判斷是否為素數(shù)
func isPrime(intChan, primeChan chan int) {var isPrime bool // 標識是否是素數(shù)for v := range intChan {isPrime = truefor i := 2; i < v; i++ {if v%i == 0 {isPrime = falsebreak}}if isPrime { // 如果為素數(shù),則往primeChan中寫入數(shù)據(jù)primeChan <- v}}fmt.Println("isPrime 讀取素數(shù)完畢")wg.Done() // 執(zhí)行完一個線程后,調用這個方法,主線程中需要等待執(zhí)行的協(xié)程數(shù)量-1
}func main() {//管道()//協(xié)程和管道應用1()//協(xié)程和管道應用2()//管道注意細節(jié)()
}func 管道() {// 創(chuàng)建一個可以存放3個int類型的管道var intChan chan int// 因為channel是引用類型,它的值其實是一個地址,然后這個地址指向的就是管道隊列;然后intChan本身也有一個地址intChan = make(chan int, 3)fmt.Printf("intChan 的值=%v intChan 本身的地址=%p\n", intChan, &intChan) // intChan 的值=0xc00006e080 intChan 本身的地址=0xc00004c020// 向管道寫入數(shù)據(jù),寫入、讀取管道數(shù)據(jù)時,用 <- 表達式intChan <- 10num := 200intChan <- num// 設置的管道容量是3,最多只能往里面寫入3條數(shù)據(jù)(長度不能超過容量)intChan <- 100// 管道的長度和cap(容量)fmt.Printf("管道 長度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 3,3// 讀取管道的數(shù)據(jù)。從管道中取出了數(shù)據(jù),可以再往里面放數(shù)據(jù)//<-intChan // 可以直接這么寫,也是取出數(shù)據(jù);不用變量接收,把取出的數(shù)據(jù)扔了不要n1 := <-intChan // 這里取出來的是最先寫入到管道里的數(shù)據(jù)(先進先出)fmt.Println("n1=", n1) // 10fmt.Printf("管道 長度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 2,3// 取出了一條數(shù)據(jù)后再往里面放一條數(shù)據(jù)intChan <- 500close(intChan) // 關閉管道,這時就不能再往管道里面寫入數(shù)據(jù)了,但是讀取沒問題fmt.Printf("管道 長度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 3,3// 在沒有使用協(xié)程的情況下,如果管道數(shù)據(jù)已經全部取出,再取會報錯n2 := <-intChann3 := <-intChanfmt.Printf("n2 = %v n3 = %v\n", n2, n3) // 200,100fmt.Printf("管道 長度 = %v 容量 = %v\n", len(intChan), cap(intChan)) // 1,3// 遍歷管道intChan2 := make(chan int, 100)for i := 0; i < 100; i++ {intChan2 <- i * 2}close(intChan2) // 管道寫完數(shù)據(jù)后,先將管道關閉,再進行遍歷// 不能用取長度的方式來遍歷管道,因為管道每取一次,長度就會變,要用 range 方式遍歷for v := range intChan2 { // 這里只返回一個數(shù)據(jù),管道里面沒有下標fmt.Println("v =", v)}
}func 協(xié)程和管道應用1() {// 創(chuàng)建兩個管道intChan := make(chan int, 10)wg.Add(2) // 說明開啟了兩個線程// 開啟了兩個協(xié)程,writeData和readData應該是交叉執(zhí)行的go writeData(intChan) // 開啟一個協(xié)程,往 intChan 中寫入數(shù)據(jù)go readData(intChan) // 開啟一個協(xié)程,讀取 intChan 的數(shù)據(jù)wg.Wait() // 告訴主線程需要等待協(xié)程執(zhí)行完畢fmt.Println("程序執(zhí)行完畢!")
}func 協(xié)程和管道應用2() {// 需求:要求統(tǒng)計1-8000的數(shù)字中,哪些是素數(shù)// 將統(tǒng)計素數(shù)的任務,分配給4個協(xié)程去完成intChan := make(chan int, 1000) // 讀寫1-8000數(shù)字的管道primeChan := make(chan int, 2000) // 存儲素數(shù)的管道wg.Add(5) // 下面開啟了5個協(xié)程// 開啟寫入 1-8000 數(shù)字的協(xié)程go func() {for i := 1; i <= 8000; i++ {intChan <- i}close(intChan)wg.Done()}()// 開啟4個讀取 1-8000 數(shù)字,并統(tǒng)計素數(shù)的協(xié)程for i := 0; i < 4; i++ {go isPrime(intChan, primeChan)}wg.Wait() // 等待協(xié)程執(zhí)行完畢close(primeChan) // 關閉 primeChan 管道// 遍歷primeChan,把結果取出來for v := range primeChan {fmt.Printf("素數(shù)是 = %v\n", v)}
}func 管道注意細節(jié)() {intChan := make(chan int, 10)for i := 0; i < 10; i++ {intChan <- i}stringChan := make(chan string, 5)for i := 0; i < 5; i++ {stringChan <- "hello" + fmt.Sprintf("%d", i)}// 傳統(tǒng)的方法在遍歷管道時,如果不關閉會阻塞而導致 deadlock// 在實際開發(fā)中,可能不好確定什么時候關閉管道,這時可以使用select方式解決for {select {// 這里如果intChan一直沒有關閉,也不會一直阻塞而導致deadlock// 如果一個case取不到數(shù)據(jù),會自動到下一個case中取case v := <-intChan:fmt.Println("從intChan讀取的數(shù)據(jù)=", v)case v := <-stringChan:fmt.Println("從stringChan讀取的數(shù)據(jù)=", v)default:fmt.Println("都取不到了")return}}
}
文件
文件這塊沒啥好說的,拿到函數(shù)直接用就行。需要注意一點就是文件file是一個指針類型。
package mainimport ("bufio""fmt""io""os"
)func main() {//基本使用讀()基本使用寫()
}func 基本使用讀() {// 打開文件file, err := os.Open("C:\\Users\\Administrator\\Desktop\\1.txt")if err != nil {fmt.Println("文件打開錯誤:", err)}//fmt.Printf("file=%v", file) // 輸出的是一個地址defer file.Close() // 當函數(shù)退出時,要關閉file,否則會有內存泄露// 創(chuàng)建一個Reader,是帶緩沖,默認緩沖區(qū)為4096(這種方式比較適合大文件讀取)reader := bufio.NewReader(file)for {str, err := reader.ReadString('\n')if err == io.EOF { // io.EOF表示讀到了文件末尾,這時就可以退出循環(huán)了break}fmt.Print(str)}fmt.Println("文件讀取完成")// ioutil.ReaderFile,一次性將文件讀取到位 這種方法適合讀取比較小的文件// 不過新版本 ioutil.ReadFile 已經棄用了,這個函數(shù)其實調用的就是 os.ReadFilecontent, err := os.ReadFile("C:\\Users\\Administrator\\Desktop\\學習計劃.txt")if err != nil {fmt.Printf("文件讀取失敗:%v", err)}//fmt.Println(content) // content是一個 []bytefmt.Println(string(content)) // 所以要轉成string}func 基本使用寫() {filePath := "C:\\Users\\Administrator\\Desktop\\測試.txt"/*OpenFile 第二個參數(shù):文件打開模式O_RDONLY int = syscall.O_RDONLY // 只讀O_WRONLY int = syscall.O_WRONLY // 只寫O_RDWR int = syscall.O_RDWR // 讀寫O_APPEND int = syscall.O_APPEND // 追加O_CREATE int = syscall.O_CREAT // 如果不存在就創(chuàng)建O_EXCL int = syscall.O_EXCL // 文件必須不存在O_SYNC int = syscall.O_SYNC // 同步ioO_TRUNC int = syscall.O_TRUNC // 打開時清空文件(一般用于覆蓋寫入)第三個參數(shù)只作用于linux系統(tǒng),Windows系統(tǒng)不起作用*/file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)if err != nil {fmt.Printf("文件打開失敗:%v", err)}defer file.Close()str := "hello,Golang\n"// NewWriter 帶緩沖區(qū)的寫入,寫完之后要用flush刷新。writer := bufio.NewWriter(file)for i := 0; i < 5; i++ {writer.WriteString(str)}writer.Flush()
}
ok,以上就是本篇的全部內容了。下一篇可能是關于網絡請求相關的,也有可能是Gorm相關的。