go 学习笔记(六)
文章目录
前言
接触了新的语言go,记录一下学习的笔记方便日后温故知新。
一、抽象
1.存取款demo
import (
"go_study_demo/go-study-base/bean"
)
func main() {
account := bean.Account{
Number: "12345",
Pwd: "123",
Balance: 100.00,
}
account.Deposit("123", 50)
account.WithDrop("123", 20)
account.Query("123")
}
二、继承(解决代码复用难题,使用匿名结构体实现继承的效果)
1.一个结构体的field和另一个处于类似或包含关系的时候可以使用匿名结构体达成继承的效果来解决代码冗余难题
2.在golang中,如果一个结构体嵌入了另一个匿名结构体那么这个结构体就可以直接访问匿名结构体的field和方法,从而实现了继承的特性
3.匿名结构体的继承方法赋值遵从就近原则,当赋值时,匿名结构体和结构体有相同的field,那么使用结构体.field的时候会优先赋值给结构体而不是
赋值给匿名结构体,如果想赋值给匿名结构体需要赋值时候 结构体.匿名结构体.field
import (
"go_study_demo/go-study-base/bean"
)
//编写一个Object,给出 getter,setter,toString
func main() {
var adult bean.Adult
//adult := bean.Adult{}
//adult := &bean.Adult{}
adult.Age = 12
//adult.Person.Age = 12
adult.Name = "张三"
adult.Wife = "李四"
adult.Say()
adult.Marry()
}
4.在嵌套多个匿名结构体时,除开结构体外的匿名结构体们存在相同的field,那么赋值时,需要指明某个匿名结构体否则无法通过编译5
.匿名结构体也可以嵌套指针类型
func main() {
child := bean.Child{Friend: "王二麻子", Person: &bean.Person{
Name: "赵六",
Age: 12,
}}
child.Say()
child.Play()
fmt.Println("\n child = ", *child.Person)
}
//结果
我的名字是赵六,我今年12岁
我和 王二麻子 一起玩
child = {赵六 12}
三、接口(interface)
1.编写一个接口,定义其中的方法,然后编写两个结构体分别实现接口内的所有方法,然后编写第三个结构体,第三个结构体
入参为接口类型,然后编写该结构体的方法,调用接口内的方法实现面向接口变成
func main() {
charger := myInterface.Charger{}
phone := myInterface.Phone{}
charger.Working(phone)
computer := myInterface.Computer{}
charger.Working(computer)
}
2.golang的接口内不可以定义常量
3.实现了接口,即实现了这个接口的所有方法,不需要显示的使用任何关键字//参考sort
4.接口本身不创建实例,但是可以指向一个实现该接口的变量
charger := myInterface.Charger{}
phone := myInterface.Phone{}
var u myInterface.Usb
u = phone
charger.Working(u)
结果
手机开始工作Start
手机停止工作Stop
5.只要是自定义数据类型,就可以实现接口比如 可以自定义一个 integer ,type integer int,然后integer去实现接口的所有方法
6.一个结构体可以实现多个接口,就把两个接口的方法全部实现即可
func main() {
charger := myInterface.Charger{}
computer := myInterface.Computer{}
charger.Working(computer, computer)
}
//结果
电脑开始工作Start
电脑停止工作Stop
do not talk about nothing
7.interface 是引用类型,是指针,默认值是nil
8.空接口interface{},没有任何方法,所有的类型都实现了空接口类型Object类
9.实践
//接口实现 sort.sort()
intSlice := []int{55, 2, 3, 9, 34, 12}
fmt.Println(intSlice) // [55 2 3 9 34 12]
sort.Ints(intSlice)
fmt.Println(intSlice) // [2 3 9 12 34 55]
10.接口可以作为继承的补充,举例当猴子类为父类,小猴子为子类时,特殊的一个子类小猴子需要拥有飞翔方法,但猴子类不应该有飞行
属性,所以改动猴子类是不合适的,此时加入飞行接口,让其中一只小猴子实现接口,既满足需求又没有破坏猴子的继承关系
实现系统接口 sort(),完成对自定义结构体的排序
func main() {
//对结构体切片进行排序,声明一个person然后在声明一个person的切片类型
var Persons PersonSlice
for i := 0; i < 5; i++ {
p := Person{
Name: fmt.Sprintf("张三~%d", rand.Intn(100)),
Age: rand.Intn(100), //返回0 - 100 的随机数
}
Persons = append(Persons, p)
}
for _, person := range Persons {
fmt.Println("排序前 - :", person)
}
//排序后
sort.Sort(Persons)
for _, person := range Persons {
fmt.Println("排序后 ~ :", person)
}
}
// Person public的结构体Person是可以被外部访问的
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
type PersonSlice []Person
func (p PersonSlice) Len() int {
return len(p)
}
//Less 决定使用什么标准排序 , 此处按照 p[i].Age 的年龄从小到达排序
func (p PersonSlice) Less(i, j int) bool {
return p[i].Age > p[j].Age
}
func (p PersonSlice) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
//分别实现 Interface 接口中的 Len() int,Less(i, j int) bool,Swap(i, j int) 三个方法
//type Interface interface {
// Len() int
// Less(i, j int) bool
// Swap(i, j int)
//}
四、多态
1.多态参数,参考上面的倒数第二个demo,其中入参 (usb Usb) 就是多态参数
2.多态数组,因为一个数组必须放同一种类型的变量,所以可以把多个变量实现同一个接口,然后放进去
func main() {
//定义一个Usb接口数组,可以存放Computer和Phone
var usbArr [3]myInterface.Usb
// var usbArr [3]interface{}
usbArr[0] = myInterface.Computer{Name: "联想"}
usbArr[1] = myInterface.Phone{Name: "华为"}
usbArr[2] = myInterface.Computer{Name: "苹果"}
for _, usb := range usbArr {
fmt.Println(usb)
}
}
//结果
{联想}
{华为}
{苹果}
五、类型断言
1.可以说好处非常大大的,不必细说
func main() {
//定义一个Usb接口数组,可以存放Computer和Phone
var usbArr [3]myInterface.Usb
// var usbArr [3]interface{}
usbArr[0] = myInterface.Computer{Name: "联想"}
usbArr[1] = myInterface.Phone{Name: "华为"}
usbArr[2] = myInterface.Computer{Name: "苹果"}
//利用接口中断言来自动化的输出不同结构体的方法
//func (c Charger) Working(usb Usb) {
// //通过usb接口变量调用 .Start() , .Stop()
// usb.Start()
// usb.Stop()
// //类型断言 语句解释:如果ok := usb.(Computer)为真,则 ok为true,并将usb转成Computer类型并赋给c
// if c, ok := usb.(Computer); ok {
// c.Talk()
// }
//}
charger := myInterface.Charger{}
for _, usb := range usbArr {
charger.Working(usb)
}
}
2.实践2
func main() {
//定义一个Usb接口数组,可以存放Computer和Phone
var ObjectArr [5]interface{}
ObjectArr[0] = 1
ObjectArr[1] = "hello"
ObjectArr[2] = 32.43
ObjectArr[3] = &myInterface.Phone{Name: "华为"}
ObjectArr[4] = myInterface.Computer{Name: "联想"}
myInterface.TypeJudge(ObjectArr[0], ObjectArr[1], ObjectArr[2], ObjectArr[3], ObjectArr[4])
//item number is 0 and type is int , value is 1
//item number is 1 and type is string , value is hello
//item number is 2 and type is float , value is 32.43
//item number is 3 and type is *Phone , value is &{华为}
//item number is 4 and type is Computer , value is {联想}
}
// TypeJudge 传入数据类型,打印属性
func TypeJudge(items ...interface{}) {
for i, item := range items {
switch item.(type) {
case bool:
fmt.Printf("item number is %d and type is bool , value is %v \n", i, item)
case int:
fmt.Printf("item number is %d and type is int , value is %v \n", i, item)
case float32, float64:
fmt.Printf("item number is %d and type is float , value is %v \n", i, item)
case string:
fmt.Printf("item number is %d and type is string , value is %v \n", i, item)
case nil:
fmt.Printf("item number is %d and type is nil , value is %v \n", i, item)
case Computer:
fmt.Printf("item number is %d and type is Computer , value is %v \n", i, item)
case Phone:
fmt.Printf("item number is %d and type is Phone , value is %v \n", i, item)
case *Phone:
fmt.Printf("item number is %d and type is *Phone , value is %v \n", i, item)
case *Computer:
fmt.Printf("item number is %d and type is *Computer , value is %v \n", i, item)
default:
fmt.Printf("item number is %d and type is unknow , value is %v \n", i, item)
}
}
}
六、文件操作
1.文件 file 是一个指针类型 ,文件打开要记得关闭,在 https://studygolang.com/pkgdoc 的os包中的type File 内的函数
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
可见入参是指针类型
2.打开文件
func main() {
//打开一个文件 D:\MyDesktop\hel11o.txt
file, err := os.Open("D:\\MyDesktop\\hello.txt")
//没有引用指向时关闭 , 关闭file指针,file对象,file句柄
if err != nil {
fmt.Println(err)
}
//输出文件是什么 //file=&{0xc00010e780}
fmt.Printf("file=%v", file)
//使用完毕后关闭资源
err = file.Close()
if err != nil {
fmt.Println(err)
}
}
2.使用带缓冲区的的方式读取文件内容并将内容输出到终端
func main() {
//打开一个文件 D:\MyDesktop\hel11o.txt
file, _ := os.Open("D:\\MyDesktop\\hello.txt")
defer file.Close()
//bufio 包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
//NewReader创建一个具有默认大小缓冲、从r读取的*Reader。
//NewReader缓冲是有大小的,所以循环的读取
fileReader := bufio.NewReader(file)
for {
//fileReader.ReadString('') 意思就是说读到一个 \n 就结束然后开始下一个循环
//但是有一个不好的地方就是说需要在文件末尾以回车结尾
//readString, err := fileReader.ReadString('\n')
readString, _, err := fileReader.ReadLine()
//io.EOF表示读取到文件的末尾
if err == io.EOF {
break
}
//fmt.Print(readString)
fmt.Println(string(readString))
}
}
//结果
大家好我是渣渣灰
我不喜欢开飞机
hello,world!
3.直接读取小文件
func main() {
//直接打开一个文件,如果文件过大效率会很低
if dir, err := ioutil.ReadFile("D:/MyDesktop/hello.txt"); err != nil {
fmt.Println("err=", err)
} else {
fmt.Println(string(dir))
//大家好我是渣渣灰
//我不喜欢开飞机
//hello,world!
}
}
3.func openFile()
func OpenFile
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。
查看常量 https://studygolang.com/pkgdoc
Constants
const (
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 // 如果不存在将创建一个新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
)
type FileMode
type FileMode uint32
FileMode代表文件的模式和权限位。这些字位在所有的操作系统都有相同的含义,因此文件的信息可以在不同的操作系统之间安全的移植。不是所有的位都能用于所有的系统,唯一共有的是用于表示目录的ModeDir位。
FileMode 只限于linux unix
const (
// 单字符是被String方法用于格式化的属性缩写。
ModeDir FileMode = 1 << (32 - 1 - iota) // d: 目录
ModeAppend // a: 只能写入,且只能写入到末尾
ModeExclusive // l: 用于执行
ModeTemporary // T: 临时文件(非备份文件)
ModeSymlink // L: 符号链接(不是快捷方式文件)
ModeDevice // D: 设备
ModeNamedPipe // p: 命名管道(FIFO)
ModeSocket // S: Unix域socket
ModeSetuid // u: 表示文件具有其创建者用户id权限
ModeSetgid // g: 表示文件具有其创建者组id的权限
ModeCharDevice // c: 字符设备,需已设置ModeDevice
ModeSticky // t: 只有root/创建者能删除/移动文件
// 覆盖所有类型位(用于通过&获取类型位),对普通文件,所有这些位都不应被设置
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice
ModePerm FileMode = 0777 // 覆盖所有Unix权限位(用于通过&获取类型位)
)
4.实践,在已存在的路径上创建文件
func main() {
//实践:创建一个文件写入 hello,world!,用带缓存的writer写入
fileName := "hello.txt"
path := "D:/MyDesktop/demo/"
filePath := path + fileName
// O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件,参数 perm在windows下无效,int 1用以占位
// O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
// 打开模式可以多样组合,中间用 | 连接
os.MkdirAll(path, 1)
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 1)
defer file.Close()
if err != nil {
fmt.Println("err=", err)
return
}
//准备写入的str
str := "大家好,我是渣渣灰!\nhello,world!"
//写入时,使用带缓存的*Writer
writer := bufio.NewWriter(file)
writer.WriteString(str)
//因为Writer是带缓存的,此时str先写入了缓存还没有落到磁盘上,写入完毕需要使用Flush(),将缓冲数据写入到磁盘上
writer.Flush()
}
5.重新输入已经存在的内容
func main() {
//实践:创建一个文件写入 hello,world!,用带缓存的writer写入
fileName := "hello.txt"
path := "D:/MyDesktop/demo/"
filePath := path + fileName
// O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件,参数 perm在windows下无效,int 1用以占位
// O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
// 打开模式可以多样组合,中间用 | 连接
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 1)
defer file.Close()
if err != nil {
//Access is denied. 表示文件操作权限不够,需要对文件进行授权
//chmod -r 644 xxx(文件路径) 或 对文件右键,点击属性,进入安全,点击高级,修改文件为读写,最后点击应用,确定。
fmt.Println("err=", err)
return
}
//准备写入的str 有的编辑器认识 \n 有的认识 \r 所以我们都带上
str := "大家好,我不是渣渣灰!\nhello,world!"
//写入时,使用带缓存的*Writer
writer := bufio.NewWriter(file)
writer.WriteString(str)
//因为Writer是带缓存的,此时str先写入了缓存还没有落到磁盘上,写入完毕需要使用Flush(),将缓冲数据写入到磁盘上
writer.Flush()
}
6.实践:创建一个文件写入 hello,world!,用带缓存的writer写入
func main() {
//实践:创建一个文件写入 hello,world!,用带缓存的writer写入
filePath := "D:/MyDesktop/demo/hello.txt"
//O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件,参数 perm在windows下无效,int 1用以占位 // fmt.Print(reflect.TypeOf(err.Error()))
fileInfo, err := os.OpenFile(filePath, os.O_CREATE, 0666)
fmt.Println(syscall.EISDIR)
fmt.Print(err)
fileInfo.Close()
}
7.读取两个已经存在的文件,将一个文件的内容写入到另一个文件
func main() {
//读取两个已经存在的文件,将一个文件的内容写入到另一个文件
filePath1 := "F:/kkk.txt"
filePath2 := "F:/aaa.txt"
file, err1 := ioutil.ReadFile(filePath1)
if err1 != nil {
fmt.Printf("file is %v \n", file)
return
}
err2 := ioutil.WriteFile(filePath2, file, 0666)
if err2 != nil {
fmt.Printf("err2 is %v \n", err2)
return
}
}
8.判断文件,目录是否存在
func Stat(name string) (FileInfo, error){ ...}
如果返回的error为nil,则说明文件或文件夹存在
如果返回的error用os.IsNotExist()判断为true,说明文件或文件夹不存在
如果返回为其他类型则不确定是否存在
根据这个特性我们自己写一个函数,判断当前路径或文件是否存在
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
9.拷贝文件
// 位于 io 包的 copy 函数
// 实践:把桌面的 bilibili.png 赋值到 G:\ 下
func main() {
//复制 文件 D:\MyDesktop\bilibili.png 到 G:\images\helloBilibili.png
srcPath := "D:\\MyDesktop\\bilibili.png"
dstPath := "G:\\images\\helloBilibili.png"
_, err := CopyFile(srcPath, dstPath)
if err != nil {
fmt.Printf("文件复制失败 err = %v\n", err)
} else {
fmt.Println("文件复制成功")
}
}
func CopyFile(srcPath, dstPath string) (written int64, err error) {
//为最终调用 func Copy(dst Writer, src Reader) (written int64, err error) ,需要获取dst Writer与src Reader
//首先打开 src 的 Reader
//获取文件句柄
openSrcPath, err := os.Open(srcPath)
//关闭文件句柄
defer openSrcPath.Close()
if err != nil {
fmt.Printf("open err is : %v\n", err)
return
}
//获取 Reader
srcReader := bufio.NewReader(openSrcPath)
//获取写文件的句柄 只写 | 没有的话就新建一个 066在windows没有效果,在linux内可以chmod
fileDstPath, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE, 0666)
//关闭句柄
defer fileDstPath.Close()
if err != nil {
fmt.Printf("OpenFile err is : %v\n", err)
return
}
//获取 Writer
dstWriter := bufio.NewWriter(fileDstPath)
//最终调用 func Copy(dst Writer, src Reader)
return io.Copy(dstWriter, srcReader)
}
10.统计字符数量
统计一个文件里面的中文,数字,英文,空格的数量
func main() {
//统计一个文件里面的中文,数字,英文,空格的数量
//思路:打开一个文件,创建一个Reader,然后遍历这个Reader每读取一行就统计改行有多少个需要统计的数据,然后将结果保存到结构体当中
//打开需要读取的文件
FileName := "D:\\MyDesktop\\新建文本文档.txt"
//打开文件句柄
openFile, err := os.Open(FileName)
//关闭文件句柄
defer openFile.Close()
if err != nil {
fmt.Printf("open file error msg is : %v \n", err)
return
}
//创建一个结构体接收结果
var count CharCount
//创建一个Reader
reader := bufio.NewReader(openFile)
//循环读取文件内容
for {
readString, err := reader.ReadString('\n')
//读完了就结束循环
if err == io.EOF {
break
}
//遍历字符串
for _, char := range readString {
switch {
case char >= 'a' && char <= 'z':
fallthrough //穿透到下一个case统计
case char >= 'A' && char <= 'Z':
count.ChCount++
case char == ' ' || char == '\t':
count.SpaceCount++
case char >= '0' && char <= '9':
count.NumCount++
default:
count.OtherCount++
}
}
}
//输出统计结果
fmt.Printf("这个文件的数字有%v个,空格有%v个,字母有%v个,其他文字有%v个", count.NumCount, count.SpaceCount, count.ChCount, count.OtherCount)
}
type CharCount struct {
ChCount int `json:"chCount,omitempty"` //英文数量
NumCount int `json:"numCount,omitempty"` //数字数量
SpaceCount int `json:"spaceCount,omitempty"` //空格数量
OtherCount int `json:"otherCount,omitempty"` //其他数量
}
七、命令行参数
1.os.Args 是一个string切片保存命令行所有的参数
func main() {
for _, arg := range os.Args {
fmt.Println("命令行参数有:", arg)
}
//命令行参数有: C:\Users\liuxin\AppData\Local\Temp\go-build1496045757\b001\exe\stuyd_demo_1.exe
//命令行参数有: tom
//命令行参数有: lili
//命令行参数有: xiaoming
}
2.根据指定key获取命令行参数的value,用flag包下的stringXXX来获取命令行参数指定key的value
func main() {
//获取以下几个参数
var connection Connection
flag.StringVar(&connection.user, "u", "", "用户名默认为空")
flag.StringVar(&connection.pwd, "pwd", "", "密码默认为空")
flag.StringVar(&connection.host, "h", "", "host默认为空")
flag.IntVar(&connection.port, "port", 3306, "端口号默认为3306")
//解析注册的flag并赋值
flag.Parse()
fmt.Print(connection)
//PS E:\GoWorkSpace\src\go_study_demo\go-study-base> go run .\stuyd_demo_1.go -u zhangsan -pwd root -h 192.168.23.22 -port 2259
//{zhangsan root 192.168.23.22 2259}
type Connection struct {
user string `json:"user,omitempty"`
pwd string `json:"pwd,omitempty"`
host string `json:"host,omitempty"`
port int `json:"port,omitempty"`
}
八、json
1.javaScrip ObjectNotation js的标记语言,主流的数据传输格式
2.json序列化
func main() {
//获取以下几个参数
var connection Connection
flag.StringVar(&connection.User, "u", "", "用户名默认为空")
flag.StringVar(&connection.Pwd, "pwd", "", "密码默认为空")
flag.StringVar(&connection.Host, "h", "", "host默认为空")
flag.IntVar(&connection.Port, "port", 3306, "端口号默认为3306")
//解析注册的flag并赋值
flag.Parse()
fmt.Println(connection)
bytes, err := json.Marshal(connection)
if err == nil {
fmt.Printf("序列化后的值为 : %v \n", string(bytes))
}
}
type Connection struct {
User string `json:"user---IsMyUser,omitempty"` //使用反射机制来实现通过user---IsMyUser修改User
Pwd string `json:"pwd,omitempty"`
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
}
//{zhangsan root 192.168.23.22 2259}
//序列化后的值为 : {"user---IsMyUser":"zhangsan","pwd":"root","host":"192.168.23.22","port":2259}
3.反序列化
func main() {
//获取以下几个参数
var connection Connection
flag.StringVar(&connection.User, "u", "", "用户名默认为空")
flag.StringVar(&connection.Pwd, "pwd", "", "密码默认为空")
flag.StringVar(&connection.Host, "h", "", "host默认为空")
flag.IntVar(&connection.Port, "port", 3306, "端口号默认为3306")
//解析注册的flag并赋值
flag.Parse()
bytes, err := json.Marshal(&connection)
if err == nil {
fmt.Printf("序列化后的值为 : %v \n", string(bytes))
}
var connection2 Connection
err = json.Unmarshal(bytes, &connection2)
if err == nil {
fmt.Printf("反序列化后的值为 : %v \n", connection2)
}
}
type Connection struct {
User string `json:"user---IsMyUser,omitempty"`
Pwd string `json:"pwd,omitempty"`
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
}
//序列化后的值为 : {"user---IsMyUser":"zhangsan","pwd":"root","host":"192.168.23.22","port":2259}
//反序列化后的值为 : {zhangsan root 192.168.23.22 2259}
九、testing单元测试
1.go自带的轻量级测试工具 testing,具备功能和性能测试功能
2.函数是可运行的,结果是可运行的,性能是好的
3.stuyd_demo_1_test
go test -v 命令会自动运行当前目录下 _test 结尾的 .go 文件,来执行以 Test 开头的测试函数
4.go test命令会把当然_test 结尾的 .go 文件全部的Test 开头的测试函数执行,然后返回pass或者fail总结性的输出测试通过或失败
十、goroutine(协程)和channel(管道)
1.统计 1~20000 中那些是素数
2.我理解并发就是假的并行
多线程在单核上运行就是并发
多线程在多核上运行就是并行
3.go主线程也可以叫线程,类比进程
go的协程类比线程,go的主线程可以跑很多很多上万个协程很轻松,协程是轻量级的线程[编译器做优化]
协程:
有独立的栈空间
共享程序的堆空间
调度由用户控制(但是进程不行,用户去start了之后,具体什么时候开展是由程序来决定的)
协程是轻量级的线程
4.案例入门
在主线程开启协程然后协程输出 hello world 主线程输出 hello golang 输出 10 次后退出程序,主协两个线程同时执行然后画出流程图
//编写一个每隔一秒钟输出一个hello world的函数
func world() {
for i := 0; i < 10; i++ {
fmt.Println("world() hello world", i)
time.Sleep(time.Second)
}
}
func main() {
go world() //要在主线程的内容之前跑
for i := 0; i < 10; i++ {
fmt.Println("main() hello golang", i)
time.Sleep(time.Second)
}
}
5.主线程是物理线程是作用在cpu上的,是重量级的非常耗费cpu资源.协程是主线程开启的,是轻量级的的线程是逻辑态,对资源消耗较小
golang的协程机制是重要的特点.可以轻松开启上万个线程其他语言的并发机制是基于进程的,开启过多的线程资源耗费过大
6.goroutine(协程)的调度模型 MPG
MPG:
M:操作系统的主线程,物理线程
P:协程执行需要的上下文
G:协程
6.设置运行的cpu的数目
func main() {
//获取逻辑cpu数目
cpu := runtime.NumCPU()
fmt.Println("本台机器的逻辑cpu数量是:", cpu)
//func GOMAXPROCS
//func GOMAXPROCS(n int) int
//GOMAXPROCS设置可同时执行的最大CPU数,并返回先前的设置。 若 n < 1,它就不会更改当前设置。本地机器的逻辑CPU数可通过 NumCPU 查询。本函数在调度程序优化后会去掉。
runtime.GOMAXPROCS(cpu-1)
}
7.lock 重量级锁,下一章channel轻量级的解决资源竞争问题(引出更优雅的channel)
//解决资源竞争问题可以考虑给资源加锁或者列队等待加锁的效率比较低,channel更好
//package sync
//import "sync"
//
//sync包提供了基本的同步基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。
//
//本包的类型的值不应被拷贝。
//首先定义一个全局的map
var (
myMap = make(map[int]int, 10)
//加一个全局互斥锁
//Mutex是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁。
lock sync.Mutex
)
//设计一个函数,函数传入 n ,返回n的阶乘结果并将结果放入到 map 当中
func test(n int) {
res := 1
for i := 1; i <= n; i++ {
res *= i
}
lock.Lock() // 写之前加锁
myMap[n] = res
lock.Unlock() //写完解锁
}
func main() {
//实践:计算 1~200 的阶乘并将结果放入map并打印出来,启动多个协程统一的访问map,将结果放入map,map应该是全局的
//开启多个协程 相当于启动了 200 个协程来计算
for i := 0; i <= 20; i++ {
go test(i)
}
lock.Lock() // 主线程并不能够获取协程状态,所以不加锁还是会产生资源竞争,加上锁就好了
for i, v := range myMap {
fmt.Printf("map[%d]=%d \n", i, v)
//结果当中出现 map[25]=-7835185981329244160 等 - 号结果或出现 0 结果,是因为越位了,结果太大了 把 n 改成小于20的数字就好了
}
lock.Unlock()
}