golang基础:信道/通道,接口,Gin框架进一步学习 | 青训营笔记

332 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第4篇笔记;

信道/通道

goroutine是GO语言程序的并发体,channel(信道)就是它们之间的通信机制,即一个goroutine与另一个goroutine之间传递信息的通道,这就是信道;

每个信道都只能传递一种数据类型的数据,所以在声明时需要指定数据类型,如chan int

信道使用

func sayid(id int){
	fmt.Println(id)
}
func main(){
	var id chan int
	var ids chan int
	//初始化信道
	id=make(chan int,1)//容量为1的信道
	ids=make(chan int,10)//容量为10的信道
	//也可直接:id:=make(chan int,数字)
	id<-16//⭐⭐接收数据⭐⭐
	for i:=0;i<10;i++{
		ids<-6666
	}
	for i:=0;i<10;i++{
		go sayid(<-id)//⭐⭐发送数据⭐⭐
		id<-i//信道的容量为1,用完之后信道里面就没有数据了,
		//所以要用一次向信道中发送一次数据
	}
	for i:=0;i<10;i++{
		go sayid(<-ids)
		//信道容量为10,这里刚好够用
	}
	time.Sleep(time.Second)
}

输出结果:

image.png

信道的初始化方法make有两个参数,

  1. 参数1就是信道类型,即chan 数据类型,必须写
  2. 参数2就是信道容量,可以不写,但不写信道容量为0,所以最好也写; 信道用完需要关闭:close(信道名)

信道容量就相当于缓冲区,当信道容量为0时就相当于无缓冲通道,那么接收端就必须先于发送端准备好,确保发送完数据后立马就被接收,否则就会造成阻塞; 当信道容量大于0时就相当于缓冲信道,信道中可以存储数据,那么发送端和接收端就可以处于异步状态;

从信道中读取数据时,有多个返回值,其中第二个可以表示信道是否被关闭, x, ok := <-id如果已经被关闭,ok为false,若还没被关闭,ok为true,信道如果关闭了之后就不能再打开了,需要用时就需要再声明一个新的信道或用还没有被关闭的信道。

双向信道与单项信道

双向信道是即可以接收数据,也可以发送数据,上面我们声明的就是双向信道,通常情况下声明的就是双向信道;

单向信道就是只能接收数据或者只能发送数据,单向信道声明如下:

var xindao = make(chan int,1)
type receiver=chan<- int
type sender=<-chan int
var receiverxindao receiver=xindao//只能接受数据的单向信道
var senderxindao sender=xindao//只能发送数据的单向信道

区别在于<-符号在关键字chan的左边还是右边。

  • <-chan表示这个信道,只能从里发出数据,对于程序来说就是只读
  • chan<-表示这个信道,只能从外面接收数据,对于程序来说就是只写

用信道来做锁

可以用信道容量这个设定来达到锁的设定;

package main
import(
	"fmt"
	"time"
)
func addchannel(ch chan int,x *int){
	ch<- 666//接收数据
	*x=*x+1
	<- ch//发送数据,
	//只有把数据发送出去才能再次接收数据
	//如果还未发送数据就会执行失败,
	//那么下面的*x=*x+1就不会执行
}
func add(y *int){
	*y=*y+1
}
func main(){
	xindao:=make(chan int,1)//声明一个容量为1的信道
	var x int=0
	var y int=0
	for i:=0;i<1000;i++{
		go addchannel(xindao,&x)
		go add(&y)
	}
	time.Sleep(time.Second)
	fmt.Println(x)
	fmt.Print(y)
}

结果:

image.png x通过利用的信道构成的锁可以保证没有并发问题,y则会出现并发问题;

接口

类似于java中的接口,需要有其实现方法:

package main
import(
	"fmt"
)
//定义一个接口实现两数相加
type myadd interface{
	add(a int)(int)
	//实现方法要和接口内的方法完全一致,就如同java中的接口一样
}
//定义一个结构体oneadd(相当于一个类)
type oneadd struct{
}
//定义oneadd这个结构体内的方法
func (oa oneadd) add(a int)(int){
	return a+1;
}
//定义另一个结构体twoadd(相当于一个类)
type twoadd struct{
}
//定义oneadd这个结构体内的方法
func (ta twoadd) add(a int)(int){
	return a+2
}
func main(){
	var my myadd
	my=new(oneadd)
	fmt.Println(my.add(3))
	my=new(twoadd)
	fmt.Println(my.add(3))
}

image.png 实现接口的方法还有两种,值接收者还有指针接收者,接上面例子:

var my myadd
one:=oneadd{}//值类型
two:=&twoadd{}//地址类型
my=one//直接将值类型赋值给my
fmt.Println(my.add(6))
my=two//将指针类型赋值给my
fmt.Println(my.add(6))

一个结构体实现多个接口

一个结构体实现多个接口示例:

//一个结构体实现多个接口
type interface1 interface{
	say1()
}
type interface2 interface{
	say2()
}
type say struct{
}
func (s say)say1(){
	fmt.Println("一")
}
func (s say)say2(){
	fmt.Println("二")
}
...
//调用
var i1 interface1=&say{}
var i2 interface2=say{}
i1.say1()
i2.say2()

接口:

image.png

空接口

空接口中没有定义任何方法,golang中所有类型都默认实现空接口;

处理请求

获取GET请求参数

适用于普通方式获取参数,即类似于/welcome?firstname=Jane&lastname=Doe这种请求格式;

//可以看出,程序并未指出参数名,所以这个处理请求适用于
    //‘/welcome’(不携带参数)
    //‘/welcome?XXX=xxx&YYY=yyy&....’(其中XXX和YYY可以是firstname和lastname也可以是其他)
router.GET("/welcome",func (c *gin.Context)  {
        var firstname string=c.DefaultQuery("firstname","Admin")
        //获取参数firstname的值,如果获取不到firstname取默认值Admin
        var lastname string=c.Query("lastname")
        //是c.Request.URL.Query().Get("lastname")的简写
        c.JSON(200,gin.H{
                "message":firstname+" and "+lastname,
        })
})

测试:

image.png

获取post请求参数

router.POST("/form",func(c *gin.Context){
        myname:=c.PostForm("Name")
        //c.DefaultPostForm("Name","xxx")//去过获取不到则取默认值xxx
        c.JSON(200,gin.H{
                "message":myname,
        })
})

与java中的springmvc框架不一样,Gin框架不会帮你把post请求传来的数据自动封装在一个实体类中,而需要你自己手动一个属性一个属性进行获取;

请求参数post形式+get形式

router.POST("/postget",func(c *gin.Context){
        myname:=c.PostForm("name")
        myage:=c.Query("age")
        c.JSON(200,gin.H{
                "message":myname+":"+myage,
        })
})

测试:

image.png

上传文件

一次上传单个文件

package main
import(
	"github.com/gin-gonic/gin"
)
func main(){
	router:=gin.Default()
	//限制表单上传大小
	router.MaxMultipartMemory=8
	//上传单文件
	router.POST("/upload",func(c *gin.Context){
		file,_:=c.FormFile("file")//获取上传文件
		// fileName:=file.Filename//获取文件名
		c.SaveUploadedFile(file,"AAA.jpg")//直接写文件名将上传到与此项程序文件同级的目录下
		c.JSON(200,gin.H{
			"message":"uploadSuccessful",
		})
	})
	router.Run()
}

image.png

上传路径这里也可以指定路径,绝对路径例如:c.SaveUploadedFile(file,"E:/Pictures/go/BBB.jpg")

或者相对路径c.SaveUploadedFile(file,"../BBB.jpg")

一次上传多个文件

//一次上传多个文件
router.POST("/uploads",func(c *gin.Context){
        form,_:=c.MultipartForm()
        files :=form.File["upload[]"]
        for _,file:=range files{
                c.SaveUploadedFile(file,file.Filename)
        }
        c.JSON(200,gin.H{
                "message":"文件上传成功",
        })
})

image.png

路由分组

如果每个请求都要像上面那样每个都要单独写个处理方法那未免太冗余了,因此有了路由分组,具体代码如下:

package main
import(
	"github.com/gin-gonic/gin"
)
func hello(c *gin.Context){
	c.JSON(200,gin.H{
		"message":"hello",
	})
}
func haha(c *gin.Context){
	c.JSON(200,gin.H{
		"message":"haha",
	})
}
func sorry(c *gin.Context){
	c.JSON(200,gin.H{
		"message":"sorry",
	})
}
func pei(c *gin.Context){
	c.JSON(200,gin.H{
		"message":"pei",
	})
}
func main(){
	router:=gin.Default()
	v1:=router.Group("/v1")
	{
		v1.GET("/hello",hello)
		v1.GET("/haha",haha)
	}
	v2:=router.Group("/v2")
	{
		v2.GET("/sorry",sorry)
		v2.GET("/pei",pei)
	}
	router.Run(":80")
}

测试:

image.png