当前位置 博文首页 > shelgi的博客:go语言几个最重要知识点的总结(1)

    shelgi的博客:go语言几个最重要知识点的总结(1)

    作者:[db:作者] 时间:2021-07-28 08:44

    1.首先要说的:每个人学习都会有自己的想法和见解,所以我的总结可能只是对于我来说好理解,请见谅。

    2.大概会总结的内容

    • 1.go语言的接口(interface)
    • 2.goroutine
    • 3.channel
      下面两个会留在明天或者后天写,一天写太多字也没人愿意看
    • 4.并发安全和锁
    • 5.两个简单的入门web代码(不使用框架)

    3.正式开始

    1.go语言的接口

    接口这个词从开始学java就有接触到,所以先说说什么是接口。说的正式一点,接口(Interface)是一些方法特征的集合,而在go语言中接口就是一种抽象的数据类型。可能这样很不好理解,那就写点例子结合着讲。

    package main
    
    import "fmt"
    
    type dog struct{}
    
    func (d dog) say() {
    	fmt.Println("汪汪汪")
    }
    
    type cat struct{}
    
    func (c cat) say() {
    	fmt.Println("喵喵喵")
    }
    
    type person struct {
    	name string
    }
    
    func (p person) say() {
    	fmt.Println("啊啊啊")
    }
    
    //接口不注重类型,只注重实现的方法
    //只要实现了say()方法的类型就能成为sayer这个接口类型
    type sayer interface {
    	say()
    }
    
    func hit(arg sayer) {
    	arg.say()
    }
    
    func main() {
    	c1 := cat{}
    	hit(c1)
    	d1 := dog{}
    	hit(d1)
    	p1 := person{name: "123"}
    	hit(p1)
    }
    
    

    这个例子里面我们首先有三个结构体,狗、猫、人,并且对应的有他们叫的方法,比如狗会汪汪汪的叫。这个时候我们再定义一个接口sayer,只要实现了say()方法的数据类型就能成为sayer这个接口类型。这个时候再写个函数hit(),传入的参数就是接口类型的变量,其实也就是所有实现了say()方法的类型,然后会调用传入参数对应的say()方法。
    在这里插入图片描述
    经过这个例子再来看看接口到底有什么用: 如果像往常一样,hit()函数传入的参数是特定的某个结构体,那就只能实现某一种结构体的方法,对于其他两个结构体就要写hit2,hit3来分别实现。但是有了接口,我们可以把所有实现同一个方法的类型变成一种集合,从而可以在一个函数调用不同数据类型下的同一方法。

    然后接口大概作用知道了,再来说说接口一些进阶点的东西
    在这里强调两句话:
    1.一个类型可以实现多个接口
    2.不同的类型也可以实现同一个接口

    刚才的例子就属于不同类型同一接口,下面再来一个同一类型不同接口以及接口嵌套的例子

    package main
    
    import "fmt"
    
    type mover interface {
    	move()
    }
    
    type sayer2 interface {
    	say()
    }
    
    type Person struct {
    	name string
    	age  int
    }
    
    //指针接收者实现接口,赋值的时候只能传入指针!!!而使用值接收者则可以传入值和指针
    func (p *Person) move() {
    	fmt.Printf("%s在跑\n", p.name)
    }
    
    func (p *Person) say() {
    	fmt.Printf("%s在叫\n", p.name)
    }
    
    //一个类型可以实现多个接口
    //不同的类型也可以实现同一个接口
    //接口的嵌套
    type animal interface {
    	mover
    	sayer2
    }
    
    func main() {
    	var m animal
    	p1 := &Person{
    		name: "hahah",
    		age:  18,
    	}
    	m = p1
    	m.move()
    	m.say()
    	fmt.Println(m)
    }
    

    我定义一个人的结构体,有姓名和年纪,同时还实现了人的动和叫两个方法。此外我还有两个针对动和叫的接口mover和sayer2,这个时候我再把用animal这个接口把这两个接口嵌套,也就是说我的人这个数据类型因为实现了动和叫,也就可以是animal这个接口类型。

    在这里还有个细节要注意,如果我把我的实现方法写成

    func (p Person) move() {
    	fmt.Printf("%s在跑\n", p.name)
    }
    
    func (p Person) say() {
    	fmt.Printf("%s在叫\n", p.name)
    }
    
    

    那么主函数实例化的时候无论传入指针还是值都可以赋值给animal这个接口,但是如果像上面那样,我们的接口是指针接收者那实例化Person的时候赋值的p1只能是指针。也就是我代码中的那句指针接收者实现接口,赋值的时候只能传入指针!!!而使用值接收者则可以传入值和指针
    go语言其实在指针这块已经很友好了,通常你传入指针或者值都能帮你自动转换,只是特殊的几个地方需要自己注意。

    然后就是空接口的应用以及接口的断言

    //空接口的应用
    	//1.可以作为函数的参数,例子:fmt.Println()的参数就是空接口
    	//2.作为map的value
    	var x = make(map[string]interface{}, 10)
    	x["name"] = "xjj"
    	x["age"] = 20
    	x["hobby"] = []string{"篮球", "唱歌", "敲代码", "玩游戏", "摄影"}
    	fmt.Println(x)
    
    	//接口的值由两部分组成:具体的类型+具体类型的值
    	//接口的断言
    	//开始猜一下接口的类型
    	var i interface{}
    	i = 123
    	ret, ok := i.(string)
    	if !ok {
    		fmt.Println("不是字符串类型")
    	} else {
    		fmt.Println("是字符串类型", ret)
    	}
    
    	//使用switch进行断言
    	switch t := i.(type) {
    	case string:
    		fmt.Println("是字符串类型", t)
    	case bool:
    		fmt.Println("是bool类型", t)
    	case int:
    		fmt.Println("是int类型", t)
    	}
    

    空接口可以传入任何数据类型,所以它经常被用作传入函数的参数或者是map的value
    上面所有代码运行的结果
    在这里插入图片描述

    2.goroutine

    谈到goroutine,最先想到的肯定是并发,但是要谈并发又得从大的进程、线程说起,所以这里我就简单的说一下这些概念,毕竟我自己学操作系统的时候没认真就没特别清晰,呜呜呜。

    当我们运行一个应用的时候,那操作系统就会为这个应用程序启动一个进程。而每个进程至少包含一个线程,也就是主线程,线程呢就是执行空间,也就可以用来运行我们所写的代码。那go语言它有什么特别的呢,一般操作系统都是在物理处理器上调用线程来运行,而go它是在逻辑处理器上调用goroutine来运行。

    再就是谈谈并发和并行:用下面的例子来理解
    并发和并行
    并发:同一时间段,我同时和两个人聊天
    并行:同一时刻,我和朋友都在和老师聊天

    其实这些既然go语言的开发者都封装好了,那我们一开始还不用那么关注原理,等日后进阶一点再去看看实现的原理也更方便理解一点,所以还是先来几个例子。

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"sync"
    )
    
    func main() {
    	runtime.GOMAXPROCS(1)
    	var wg sync.WaitGroup
    	wg.Add(2)
    	fmt.Println("Start Goroutines")
    
    	//声明一个匿名函数创建goroutine
    	go func() {
    		defer wg.Done()
    
    		//显示小写字母表3次
    		for count := 0; count < 3; count++ {
    			for char := 'a'; char < 'a'+26; char++ {
    				fmt.Printf("%c ", char)
    			}
    			fmt.Println()
    		}
    	}()
    
    	//声明一个匿名函数创建goroutine
    	go func() {
    		defer wg.Done()
    
    		//显示大写字母表3次
    		for count := 0; count < 3; count++ {
    			for char := 'A'; char < 'A'+26; char++ {
    				fmt.Printf("%c ", char)
    			}
    			fmt.Println()
    		}
    	}()
    
    	//等待goroutine结束
    	fmt.Println("Waiting to finish")
    	wg.Wait()
    	fmt.Println("Finish!")
    
    }
    

    在这里插入图片描述
    这个例子是并发显示大小写字母表,其实程序是并发的,只不过第一个goroutine完成的太快,所以每次看到的都是先大写再小写。这个简单的例子里面也有些小细节需要注意。
    1.runtime.GOMAXPROCS(),这个是指定调度器的逻辑处理器数量,1.5版本之前默认是1,之后默认是全部核数,所以需要的话可以调用这个函数进行配置。

    2.sync这个包主要是用来记录维护goroutine,sync.WaitGroup是一个计数的信号量,可以记录运行的goroutine数,我们代码中Add(2)就说明我们用了两个goroutine,然后需要使用goroutine也很简单,只需要前面加上go关键字。wg.Done()就是表示任务完成,此时会把之前WaitGroup的计数量-1.

    3.wg.Wait()会等所有任务结束才停止等待,也就是等计数量为0
    还是上面那个代码的例子,为了实现上面说的“同时和两个人聊天的效果”,我们把指定的处理器数量增大,再来看看并发的效果
    在这里插入图片描述
    当我们把处理器设置为5,就可以看到大小写交替的情况了,由于随机性,尝试的时候可以多试几次。
    还有一个简单的例子也可以说明这个

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"sync"
    )
    
    //并发和并行
    //并发:同一时间段,我同时和两个人聊天
    //并行:同一时刻,我和朋友都在和老师聊天
    
    var wg sync.WaitGroup
    
    //goroutine类似于线程(用户态线程)
    func hello(i int) {
    	fmt.Println("hello goroutine", i)
    	wg.Done() //计数器-1
    }
    
    func main() {
    	runtime.GOMAXPROCS(3) //占用的cpu核数,1.5+默认使用全部核数
    	wg.Add(100)           //计数器
    	for i := 0; i < 100; i++ {
    		go hello(i)
    	}
    	fmt.Println("hello main")
    	//time.Sleep(time.Second)
    	wg.Wait() //等待计数器为0才退出
    }
    
    

    在这里插入图片描述

    3.channel

    channel通道主要是为了进行同步,当一个资源需要共享时用channel就可以在goroutine之间确保同步交换数据。
    channel有两种:无缓冲通道和有缓冲通道,区别还得从它的创建开始讲。

    unbuf:=make(chan int)//无缓冲通道
    buf:=make(chan int