您现在的位置是:首页 >技术杂谈 >7 文件操作、单元测试、goroutine【Go语言教程】网站首页技术杂谈

7 文件操作、单元测试、goroutine【Go语言教程】

NPE~ 2024-06-17 10:13:44
简介7 文件操作、单元测试、goroutine【Go语言教程】

7 文件操作、单元测试、goroutine【Go语言教程】

1 文件操作

1.1 介绍

在这里插入图片描述

  • os.File 封装所有文件相关操作,File 是一个结构体
    常用方法:
  1. 打开文件
    在这里插入图片描述
  2. 关闭文件
    在这里插入图片描述
package main

import (
	"fmt"
	"os"
)

func main(){
	//打开文件
	//file又叫做:file对象、file指针、file文件句柄
	file, err := os.Open("D:/系统默认/桌面/code.txt")
	if err != nil {
		fmt.Println("open file err=", err)
	}
	//输出file,看看file到底是什么,最后结果可以看处file就是一个指针*File
	//file=&{0xc000004a00}
	fmt.Printf("file=%v", file)
	//关闭文件
	err = file.Close()
	if err != nil {
		fmt.Println("close file err=", err)
	}
}

1.2 应用实例

①读文件

常用方法:
①bufio.NewReader(), reader.ReadString【带缓冲】
②io/ioutil【一次性读取,适用于小文件】

  1. 读取文件的内容并显示在终端(带缓冲区的方式),使用 os.Open, file.Close, bufio.NewReader(), reader.ReadString 函数和方法.
package main

import (
	"fmt"
	"os"
	"bufio"
	"io"
)

//定义缓冲区常量
const (
	defaultBufSize = 4096 //默认缓冲区为4096
)


func main(){
	//打开文件
	file, err := os.Open("D:/系统默认/桌面/code.txt")
	if err != nil {
		fmt.Println("open file err=", err)
	}
	//当函数退出时,要及时的关闭file句柄,否则会又内存泄漏问题
	defer file.Close() 
	//创建一个*Reader(带缓冲的)
	reader := bufio.NewReader(file)
	//循环读取文件内容
	for {
		str, err := reader.ReadString('
') //读取到一个换行就结束
		if err == io.EOF { //io.EOF表示文件的末尾
			break
		}
		//输出读取到的内容
		fmt.Println(str)
	}
	fmt.Println("文件读取结束....")
}

  1. 读取文件的内容并显示在终端(使用 ioutil 一次将整个文件读入到内存中),这种方式适用于文件不大的情况。相关方法和函数(ioutil.ReadFile)
package main

import (
	"fmt"
	"io/ioutil"
)

func main(){
	//使用ioutil.ReadFile一次性将文件读取到位
	file := "D:/系统默认/桌面/code.txt"
	content, err := ioutil.ReadFile(file) // content: []byte
	if err != nil {
		fmt.Printf("read file err=%v", err)
	}
	//把读取到的文件内容显示到终端
	fmt.Printf("%v", string(content))//[]byte
	//我们没有显示的Open文件,因此也不需要显示的Close文件
	//因为,文件的Open何Close都被封装到了ioutils.ReadFile的函数内部
}

效果:
在这里插入图片描述

②写文件

在这里插入图片描述

1. 创建一个新文件,写入内容[os.O_CREATE]
  • file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_CREATE, 0666)//只写、创作
  • writer:= bufio.NewWriter(file)
package main

import (
	"fmt"
	"os"
	"bufio"
)



func main(){
	//创建一个新文件,写入内容 5句 "hello Go"
	//1. 打开文件 D:/系统默认/桌面/demo1.txt
	filePath := "D:/系统默认/桌面/demo1.txt"
	//os.O_WRONLY | os.O_CREATE, 文件模式 0666 权限操作
	file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_CREATE, 0666)
	if err != nil {
		fmt.Printf("open file err=%v
", err)
		return
	}
	//及时关闭file句柄
	defer file.Close()
	//准备写入5句 "hello, Go"
	str := "hello, Go
"
	//写入时,使用带缓存的 *Writer
	writer := bufio.NewWriter(file)
	for i := 0; i < 5; i++ {
		writer.WriteString(str)
	}
	//因为writer是带缓存,因此在调用WriterString方法时,其实内容是先写入到缓存的,
	//所以需要调用Flush方法,将缓冲的数据真正写入到文件中,否则文件中会没有数据
	writer.Flush()
}
2. 覆盖原来的内容写[os.O_TRUNC]

打开一个存在的文件中,将原来的内容覆盖成新的内容 10 句 “你好,Go”

  • file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_TRUNC, 0666)
  • os.O_WRONLY | os.O_TRUNC
package main

import (
	"fmt"
	"os"
	"bufio"
)


func main(){
	//打开一个存在的文件,将原来的内容覆盖为"hello jackson"
	//1. 打开一个已经存在的文件"D:系统默认桌面demo1.txt"
	filePath := "d:/系统默认/桌面/demo1.txt"
	file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_TRUNC, 0666)
	if err != nil {
		fmt.Printf("open file err=%v
", err)
		return
	}
	//及时关闭file句柄
	defer file.Close()
	str := "hello jackson
"
	//写入时使用带缓存的 *Writer
	writer := bufio.NewWriter(file)
	for i := 0; i < 10; i++ {
		writer.WriteString(str)
	}
	//将缓存中的数据flush到硬盘
	writer.Flush()

}
3. 追加写[os.O_APPEND]

打开一个存在的文件,在原来的内容追加内容

  • file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_APPEND, 0666)
package main

import (
	"fmt"
	"os"
	"bufio"
)


func main(){
	//打开一个存在的文件,追加写入"you are good!!!"
	filePath := "d:/系统默认/桌面/demo1.txt"
	file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_APPEND, 0666)
	if err != nil {
		fmt.Printf("open file err=%v
", err)
		return
	}
	defer file.Close()
	str := "you are good!!!
"
	writer := bufio.NewWriter(file);
	for i := 0; i < 5; i++ {
		writer.WriteString(str)
	}
	writer.Flush()

}
4. 读出文件内容并追加[os.O_RDWR | os.O_APPEND]

打开一个存在的文件,将原来的内容读出显示在终端,并且追加 5 句"you are good!!!"

  • file, err := os.OpenFile(filePath, os.O_RDWR | os.O_APPEND, 0666)
  • reader := bufio.NewReader(file)
  • str, err := reader.ReadString(‘ ’)
package main

import (
	"fmt"
	"os"
	"bufio"
	"io"
)

func main(){
	//打开一个存在的文件,追加写入"you are good!!!"
	filePath := "d:/系统默认/桌面/demo1.txt"
	//READ、WRITE、APPEND
	file, err := os.OpenFile(filePath, os.O_RDWR | os.O_APPEND, 0666)
	if err != nil {
		fmt.Printf("open file err=%v
", err)
		return
	}
	defer file.Close()
	//先读取原来的内容,并显示在终端
	reader := bufio.NewReader(file)
	for {
		str, err := reader.ReadString('
')
		if err == io.EOF { //如果读取到文件的末尾
			break
		}
		//显示到终端
		fmt.Print(str)
	}
	//追加写入数据"you are good!!!"
	str := "you are good!!!"
	writer := bufio.NewWriter(file)
	for i := 0; i < 5; i++ {
		writer.WriteString(str)
	}
	writer.Flush()
}
5. 将A文件内容写入到B文件

编程一个程序,将一个文件的内容,写入到另外一个文件。注:这两个文件已经存在了.

  • 说明:使用 ioutil.ReadFile / ioutil.WriteFile 完成写文件(覆盖写)的任务.
package main

import (
	"fmt"
	_ "os"
	_ "bufio"
	_ "io"
	"io/ioutil"
)



func main(){
	//将demo.txt文件的内容导入到demo1.txt
	//1. 先将demo.txt内容读取到内存
	//2. 将读取到的内容写入到demo1.txt
	filePath1 := "d:/系统默认/桌面/demo.txt"
	filePath2 := "d:/系统默认/桌面/demo1.txt"
	data, err := ioutil.ReadFile(filePath1)
	if err != nil {
		fmt.Printf("read file err=%v
", err)
		return
	}
	//从demo.txt文件中读取到的data数据写入到demo1.txt
	err = ioutil.WriteFile(filePath2, data, 0666)
	if err != nil {
		fmt.Printf("write file err=%v
", err)
	}
}
6. 判断文件是否存在

在这里插入图片描述

  • _, err := os.Stat(path)
  • os.IsNotExist(err)
package main

import (
	"fmt"
	"os"
)



func main(){
	//判断文件或目录是否存在
	filePath1 := "d:/系统默认/桌面/demo.txt"
	flag, err := PathExists(filePath1)
	//flag=true, err=<nil>
	fmt.Printf("flag=%v, err=%v
", flag, err)
}

func PathExists(path string) (bool,  error) {
	_, err := os.Stat(path)
	if err == nil { //文件或目录存在
		return true, nil
	}
	//如果文件的Stat返回的err是os.IsNotExist则表明文件不存在
	if os.IsNotExist(err){
		return false, nil
	}
	return false, err
}

③拷贝文件、统计字符数、命令行参数

1. 拷贝一张图片

说明:将一张图片/电影/mp3 拷贝到另外一个文件 e:/abc.jpg io 包

  • func Copy(dst Writer, src Reader) (written int64, err error)
    注意; Copy 函数是 io 包提供的.
package main

import (
	"fmt"
	"os"
	"io"
	"bufio"
)



func main(){
	//D:系统默认桌面
	_, err := CopyFile("D:/系统默认/桌面/banner2.png", "D:/系统默认/桌面/banner.png")
	if err != nil {
		fmt.Printf("CopyFile() fail, err=%v", err)
	} else {
		fmt.Printf("success...")
	}
}

//拷贝文件
func CopyFile(dstFilePath string, srcFilePath string) (written int64, err error){
	srcFile, err := os.Open(srcFilePath)
	if err != nil {
		fmt.Printf("open file err=%v
", err)
	}
	defer srcFile.Close()
	//通过srcFile获取到Reader
	reader := bufio.NewReader(srcFile)
	//打开dstFilePath
	dstFile, err := os.OpenFile(dstFilePath, os.O_WRONLY | os.O_CREATE, 0666)
	if err != nil {
		fmt.Printf("open fil err=%v
", err)
		return
	}
	//通过dstFile获取到Writer
	writer := bufio.NewWriter(dstFile)
	defer dstFile.Close()
	//调用io包下的工具类
	return io.Copy(writer, reader)
}
2. 统计字符数

说明:统计一个文件中含有的英文、数字、空格及其它字符数量

package main

import (
	"fmt"
	"os"
	"io"
	"bufio"
)


//定义一个结构体,用于保存统计结果
type CharCount struct {
	ChCount int //记录英文个数
	NumCount int //数字个数
	SpaceCount int //空格个数
	OtherCount int //其他字符个数
}
func main(){
	//1. 打开一个文件,创建一个Reader
	//2. 每读取一行,就去统计该行有多少个英文、数字、空格和其他字符
	//3. 然后将结果保存到一个结构体
	fileName := "D:/系统默认/桌面/demo.txt"
	file, err := os.Open(fileName)
	if err != nil {
		fmt.Printf("open file err=%v
", err)
		return
	}
	defer file.Close()
	//定义CharCount实例
	var count CharCount
	//创建一个reader
	reader := bufio.NewReader(file)
	//开始循环读取文件的内容
	for {
		str1, err := reader.ReadString('
') //一行一行读取
		if err == io.EOF { //读到文件末尾就退出
			break
		}
		//为了兼容中文字符,可以将str转成[]rune
		str := []rune(str1)
		//遍历str,进行统计
		for _, v := range str {
			switch {
				case v >= 'a' && v <= 'z':
					fallthrough //穿透,大小写字母都存在ChCount中
				case v >= 'A' && v <= 'Z':
					count.ChCount++
				case v == ' ' || v == '	':
					count.SpaceCount++
				case v >= '0' && v <= '9':
					count.NumCount++
				default:
					count.OtherCount++
			}
		}
	}
	//输出统计的结果看看是否正确
	fmt.Printf("字符的个数为=%v, 数字的个数为=%v, 空格的个数为=%v, 其他字符的个数为=%v", 
		count.ChCount, count.NumCount, count.SpaceCount, count.OtherCount)
}
3. 命令行参数解析[flag包]

比如:cmd>main.exe -f c:/aaa.txt -p 200 -u root 这样的形式命令行,go 设计者给我们提供了 flag包,可以方便的解析命令行参数,而且参数顺序可以随意

package main

import (
	"fmt"
	"flag"
)

func main(){
	//定义几个变量,用于接收命令行的参数值
	var user string
	var pwd string
	var host string
	var port int

	//&user 就是接收用户命令行中输入的 -u 后面的参数值
	//"u", 就是 -u 指定参数
	//"", 默认值
	//"用户名,默认为空" 说明
	flag.StringVar(&user, "u", "", "用户名, 默认为空")
	flag.StringVar(&pwd, "pwd", "", "密码, 默认为空")
	flag.StringVar(&host, "h", "localhost", "主机名, 默认为localhost")
	flag.IntVar(&port, "port", 3306, "端口号, 默认为3306")
	//这里有一个非常重要的操作, 转换, 必须调用该方法
	flag.Parse()
	
	//输出结果
	fmt.Printf("user=%v pwd=%v host=%v port=%v", user, pwd, host, port)
}

在这里插入图片描述
mysql的命令行也是类似于这种方式

④json的序列化、反序列化

1. 序列化
  • json 序列化是指,将有 key-value 结构的数据类型(比如结构体、map、切片)序列化成 json 字符串的操作。
  • 这里我们介绍一下结构体、map 和切片的序列化,其它数据类型的序列化类似。
package main

import (
	"fmt"
	"encoding/json"
)

//定义一个结构体
type Monster struct {
	Name string `json:"name"`
	Age int		`json:"age"`
	Birthday string
	Sal float64
	Skill string
}

func testStruct(){
	//演示
	monster := Monster{
		Name: "牛魔王",
		Age : 500,
		Birthday : "2011-11-11",
		Sal : 8000.0,
		Skill : "牛魔拳",
	}
	//将monster序列化
	data, err := json.Marshal(&monster)
	if err != nil {
		fmt.Printf("序列号错误 err=%v
", err)
	}
	//输出序列化后的结果
	fmt.Printf("结构体:monster序列化后=%v
", string(data))
}

//将map进行序列化
func testMap(){
	var a map[string]interface{}
	//使用map之前需要make
	a = make(map[string]interface{})
	a["name"] = "红孩儿"
	a["age"] = 30
	a["address"] = "洪崖洞"
	//将a这个map进行序列化
	data, err := json.Marshal(a)
	if err != nil {
		fmt.Printf("序列化错误 err=%v
", err)
	}
	fmt.Printf("map序列化=%v
", string(data))
}

//对切片序列化 []map[string]interface{}
func testSlice(){
	var slice []map[string]interface{}
	var m1 map[string]interface{}
	//使用map前make
	m1 = make(map[string]interface{})
	m1["name"] = "jack"
	m1["age"] = "7"
	m1["address"] = "北京"
	slice = append(slice, m1)

	var m2 map[string]interface{}
	//使用map前,需要先make
	m2 = make(map[string]interface{})
	m2["name"] = "tom"
	m2["age"] = "20"
	m2["address"] = [2]string{"墨西哥", "夏威夷"}
	slice = append(slice, m2)

	data, err := json.Marshal(slice)
	if err != nil {
		fmt.Printf("序列化错误 err=%v
", err)
	}
	//输出序列化后的结果
	fmt.Printf("切片:slice序列化后=%v
", string(data))
}

//对基本数据类型序列化【意义不大】
func testFloat64(){
	var num1 float64 = 2345.67
	data, err := json.Marshal(num1)
	if err != nil {
		fmt.Printf("序列化错误 err=%v
", err)
	}
	fmt.Printf("基本数据类型:num1 序列化后=%v
", string(data))
}

func main(){
	testStruct()
	testMap()
	testSlice()
	testFloat64()
}

在这里插入图片描述

2. 反序列化

json 反序列化是指,将 json 字符串反序列化成对应的数据类型(比如结构体、map、切片)的操作

package main

import (
	"fmt"
	"encoding/json"
)

//定义一个结构体
type Monster struct {
	Name string `json:"name"`
	Age int		`json:"age"`
	Birthday string
	Sal float64
	Skill string
}

//反序列化结构体
func UnmarshalStruct(){
	//说明 str 在项目开发中,是通过网络传输获取到..  或者是读取文件获取到
	str := "{"Name":"牛魔王","Age":500,"Birthday":"2011-11-11","Sal":8000,"Skill":"牛魔拳"}"
	var monster Monster
	err := json.Unmarshal([]byte(str), &monster)
	if err != nil {
		fmt.Printf("unmarshal err=%v", err)
	}
	fmt.Printf("结构体反序列化后 monster=%v monster.Name=%v
", monster, monster.Name)
}

//反序列化map
func UnmarshalMap(){
	str := "{"address":"洪崖洞","age":30,"name":"红孩儿"}"
	var a map[string]interface{}
	//注意:反序列化map,不需要make,因为make操作被封装到Unmarshal函数
	err := json.Unmarshal([]byte(str), &a)
	if err != nil {
		fmt.Printf("unmarshal err=%v", err)
	}
	fmt.Printf("map反序列化后 a=%v
", a)
}

//反序列化切片
func UnmarshalSlice(){
	str := "[{"address":"北京","age":"7","name":"jack"}," +
		"{"address":["墨西哥","夏威夷"],"age":"20","name":"tom"}]"
	var slice []map[string]interface{}
	//反序列化不需要make,因为make操作被封装在了Unmarshal函数
	err := json.Unmarshal([]byte(str), &slice)
	if err != nil {
		fmt.Printf("unmarshal err=%v
", err)
	}
	fmt.Printf("切片反序列化后 slice=%v
", slice)
}

func main(){
	UnmarshalStruct()
	UnmarshalMap()
	UnmarshalSlice()
}

在这里插入图片描述
说明:
在这里插入图片描述

2 单元测试

2.1 概念

Go 语言中自带有一个轻量级的测试框架 testing 和自带的 go test 命令来实现单元测试和性能测试,testing 框架和其他语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。

  • go test
  • testing 测试框架
    go test [运行错误时才有日志]
    go test -v [不论运行正确还是错误,都输出日志]
  1. 测试用例文件名必须以 _test.go 结尾。 比如 cal_test.go , cal 不是固定的。
  2. 测试用例函数必须以 Test 开头,一般来说就是 Test+被测试的函数名,比如 TestAddUpper
  3. TestAddUpper(t *tesing.T) 的形参类型必须是 *testing.T 【看一下手册】
  4. 一个测试用例文件中,可以有多个测试用例函数,比如 TestAddUpper、TestSub
  5. 运行测试用例指令
    (1) cmd>go test [如果运行正确,无日志,错误时,会输出日志]
    (2) cmd>go test -v [运行正确或是错误,都输出日志]
  6. 当出现错误时,可以使用 t.Fatalf 来格式化输出错误信息,并退出程序
  7. t.Logf 方法可以输出相应的日志
  8. 测试用例函数,并没有放在 main 函数中,也执行了,这就是测试用例的方便之处[原理图].
    在这里插入图片描述
  9. PASS 表示测试用例运行成功,FAIL 表示测试用例运行失败
  10. 测试单个文件,一定要带上被测试的原文件
    go test -v cal_test.go cal.go
  11. 测试单个方法
    go test -v -test.run TestAddUpper

快速入门:

xxx_test.go文件应该与被测试的函数在同一个包下
比如:都在monster包下,在进行单元测试的时候如果用到了其他go文件中的函数、结构体、变量等,需要在go test命令后面再加上这个函数。
执行类似下面命令便可以了
go test -v .monster_test.go .monster.go
在这里插入图片描述

package test

import (
	"testing"
)

func TestSub(t *testing.T){
	res := Sub(20, 10)
	if res != 10 {
		t.Fatalf("sub(20, 10) error, 期望值为%v, 实际值为%v", 10, res)
	}
	t.Logf("test sub success...")
}

//定义一个Sub函数
func Sub(a int, b int) int {
	return a - b
}

在这里插入图片描述

2. 2 实战

在这里插入图片描述

  1. monster/monster.go:
package monster
import (
	"fmt"
	"encoding/json"
	"io/ioutil"
)


type Monster struct {
	Name string
	Age int
	Skill string
}

//序列化保存到文件中
func (this *Monster) Store() bool {
	//1. 序列化
	data, err := json.Marshal(this)
	if err != nil {
		fmt.Println("marshal err=", err)
		return false
	}
	//2. 保存到文件
	filePath := "d:/系统默认/桌面/demo.ser"
	err = ioutil.WriteFile(filePath, data, 0666)
	if err != nil {
		fmt.Println("write file err=", err)
		return false
	}
	return true
}

//反序列化
func (this *Monster) ReStore() bool {
	//1. 从序列化文件中读取字符串
	filePath := "d:/系统默认/桌面/demo.ser"
	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		fmt.Println("ReadFile err=", err)
		return false
	}
	//2. 使用读取到的data: byte[]进行反序列化
	err = json.Unmarshal(data, this)
	if err != nil {
		fmt.Println("Unmarshal err=", err)
		return false
	}
	return true
}
  1. monster/monster_test.go
package monster
import (
	"testing"
)

//测试Store方法
func TestStore(t *testing.T){
	monster := &Monster{
		Name : "红孩儿",
		Age: 10,
		Skill: "吐火",
	}
	flag := monster.Store()
	if !flag {
		t.Fatalf("monster.Store()错误,希望为=%v 实际为%v", true, flag)
	}
	t.Logf("monster.Store() 测试成功!!!")
}

func TestReStore(t *testing.T){
	var monster = &Monster{}
	flag := monster.ReStore()
	if !flag {
		t.Fatalf("monster.ReStore()错误, 希望为=%v 实际为=%v", true, flag)
	}
	//进一步判断
	if monster.Name != "红孩儿" {
		t.Fatalf("monster.ReStore()错误, 希望为=%v 实际为=%v", "红孩儿", monster.Name)
	}
	t.Logf("monster.ReStore() 测试成功!!!")
}

测结果:
在这里插入图片描述

3 goroutine

3.1 goroutine(协程)

①概念及快速入门

1. 概念
  • Go 主线程(有程序员直接称为线程/也可以理解成进程): 一个 Go 线程上,可以起多个协程,你可以这样理解,协程是轻量级的线程[编译器做优化]。
  • Go 协程的特点
  1. 有独立的栈空间
  2. 共享程序堆空间
  3. 调度由用户控制
  4. 协程是轻量级的线程
    在这里插入图片描述
  1. 进程、线程关系:
    在这里插入图片描述
    在这里插入图片描述
  2. 并发与并行
  • 多线程程序在单核上运行,就是并发
  • 多线程程序在多核上运行,就是并行
2. 快速入门

请编写一个程序,完成如下功能:

  1. 在主线程(可以理解成进程)中,开启一个 goroutine, 该协程每隔 1 秒输出 “hello,world”
  2. 在主线程中也每隔一秒输出"hello,golang", 输出 10 次后,退出程序
  3. 要求主线程和 goroutine 同时执行.
package main
import(
	"fmt"
	"time"
	"strconv"
)

//编写一个函数,每隔1s输出 "hello world"
func test(){
	for i := 1; i <= 10; i++ {
		fmt.Println("test() hello world "+ strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

func main(){
	go test() //开启了一个goroutine 协程
	for i := 1; i <= 10; i++ {
		fmt.Println("main() hello, golang " + strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

在这里插入图片描述
流程图:
在这里插入图片描述

小结:

  1. 主线程是一个物理线程,直接作用在 cpu 上的。是重量级的,非常耗费 cpu 资源。
  2. 协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。
  3. Golang 的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显Golang 在并发上的优势了

②gorountine的MPG调度模型

  1. 概念:

M:操作系统主线程(main)
P:协程执行所需上下文(Process context)
G:协程(goroutine)
在这里插入图片描述

  1. 运行状态
  • 状态1:
    在这里插入图片描述
  • 状态2:
    在这里插入图片描述

③设置cpu个数

介绍:为了充分了利用多 cpu 的优势,在 Golang 程序中,设置运行的 cpu 数目
在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。