go
数组
数组是值拷贝
两个初始化
// 数组 值拷贝
func TestArray(t *testing.T) {
var myArray [3]int
for i := range myArray {
fmt.Println(myArray[i])
}
fmt.Println("===================")
myArrary2 := [3]int{1, 2}
for i := range myArrary2 {
fmt.Println(myArrary2[i])
}
}
切片(动态数组)
func edit(array []int) {
//引用传递 切片
array[0] = 2
}
// 动态数组 切片
func TestDongtai(t *testing.T) {
myArray := []int{1, 2, 3} //动态数组
for _, value := range myArray {
//1 2 3
fmt.Println(value)
}
//切片传递的是指针
edit(myArray)
println("================")
for _, value := range myArray {
// 2 2 3
fmt.Println(value)
}
}
func TestSclice(t *testing.T) {
//声明slice是一个切片并初始化
slice := []int{1, 2, 3}
//声明slicel1是一个切片,但是并没有分配空间
var slicel1 []int
slicel1[1] = 1 //这样编译会报错,因为没有分配空间
slicel1 = make([]int, 3) //分配3空间 默认值是0
slicel1[1] = 1 //不会报错
//声明一个切片,并且分配空间
slice2 := make([]int, 3)
fmt.Println("结果", len(slice), len(slicel1), len(slice2))
//判断slice是不是一个空
// slice == nil
}
// 切片的追加
func TestCap(t *testing.T) {
//长3 容量5
var number = make([]int, 3, 5)
fmt.Printf("len = %d, cap = %d\n slice = %v\n", len(number), cap(number), number)
number = append(number, 1)
fmt.Printf("len = %d, cap = %d\n slice = %v\n", len(number), cap(number), number)
//cap如果已经满了 你再添加一个元素 切片会给我们增加一个cap容量,相当于cap是我们开辟空间的一个单位
//不定义cap就会len一样长
}
什么是值拷贝?什么是引用传递?
值拷贝就是传递过去的是一个数据副本,对它修改原本的数据并不会被修改,而引用传递,传的是地址,就可以直接修改到这个数据
长度和容量分别代表什么?
var number = make([]int, 3, 5)
就上面这个代码,切片的长度为3,容量为5。
初始化这个number的切片的容量为5,初始化了3,值为 [0,0,0]
当我们往里面添加数据的时候
number = append(number, 1)
再次查看number的长度和容量,分别为4,5
当我们再插入两个数据时,再次查看长度和容量时:
就会变成6,10。可以看出容量是切片每次扩容的单位。
map
// 传参也是引用传参
func TestMap(t *testing.T) {
//声明一个map 但是现在是一个空map
var m map[string]interface{}
//给map开辟一个空间
m = make(map[string]interface{})
m["key"] = "value"
map1 := map[string]string{
"key": "value",
}
fmt.Println(map1)
delete(map1, "key")
}
封装
声明一种行的数据类型myint,是int的一个别名
type Hero struct {
Name string
Ad int
Level int
}
func (h Hero) GetName() string {
return h.Name
}
func (h Hero) SetName(name string) {
h.Name = name
}
func (h Hero) show() {
fmt.Println(h)
}
func TestStruct(t *testing.T) {
h := Hero{"zjx", 1, 2}
h.show()
h.SetName("tyq")
h.show()
}
这样是无法修改值的,所以应该传指针
type Hero struct {
Name string
Ad int
Level int
}
func (h *Hero) GetName() string {
return h.Name
}
func (h *Hero) SetName(name string) {
h.Name = name
}
func (h *Hero) show() {
fmt.Println(h)
}
func TestStruct(t *testing.T) {
h := &Hero{"zjx", 1, 2}
h.show()
h.SetName("tyq")
h.show()
}
继承
type Humer struct {
Id int
Name string
Age int
}
type u struct {
Habby string
Humer
}
多态
type Animal interface {
Sleep()
GetColor() string // 获取动物的颜色
}
// 具体的类
type Cat struct {
color string // 猫的颜色
}
// 必须要全部实现接口
func (cat *Cat) Sleep() {
fmt.Println("cat is sleep")
}
func (cat *Cat) GetColor() string {
fmt.Println("cat is GetColor")
return cat.color
}
// 具体的类
// 具体的类
type Dog struct {
color string // 猫的颜色
}
// 必须要全部实现接口
func (dog *Dog) Sleep() {
fmt.Println("dog is sleep")
}
func (dog *Dog) GetColor() string {
fmt.Println("dog is GetColor")
return dog.color
}
func TestInterface(t *testing.T) {
var animal Animal
animal = &Cat{color: "red"}
animal.Sleep() // 猫的sleep
animal = &Dog{color: "green"}
animal.Sleep()
}
interface 类型断言
type Reader interface {
ReadBook()
}
type Writer interface {
WriteBook()
}
// 具体类型
type Book struct {
}
func (b *Book) ReadBook() {
fmt.Println("Read a book.")
}
func (b *Book) WriteBook() {
fmt.Println("Write a book.")
}
func TestPair(t *testing.T) {
b := &Book{}
var r Reader
r = b
r.ReadBook()
var w Writer
w = r.(Writer) //类型断言 r是否实现了writer这个接口 pair不变
w.WriteBook()
}
反射
pair
变量的结构
pairtype:int:11,value
pair不会改变
type User struct {
Id int
Name string
Age int
}
func (u User) Call() {
fmt.Println("user call ...")
}
func TestReflectNum(t *testing.T) {
//var num float64 = 1.234
user := User{1, "zjx", 12}
reflectNum(user)
}
// 反射
func reflectNum(arg interface{}) {
argType := reflect.TypeOf(arg)
argValue := reflect.ValueOf(arg)
fmt.Println(argType, argType.Name())
fmt.Println(argValue)
fmt.Println("==============")
//通过type获取里面的字段
for i := 0; i < argType.NumField(); i++ {
field := argType.Field(i)
value := argValue.Field(i).Interface()
fmt.Println(field, value)
}
fmt.Println("==========method")
fmt.Println(argType.NumMethod())
for i := 0; i < argType.NumMethod(); i++ {
m := argType.Method(i)
fmt.Println(m.Name, m.Type)
}
}
结构体标签
结构体标签应用
json编解码 序列化和反序列化
orm映射关系
goroutine
协程并发
协程:coroutine,轻量级线程
进程和线程的弊端
- 1.高消耗调度cpu
进程线程的数量越多,切换的成本就越大,也就越浪费【切换上下文对cpu的消耗】
多线程也会有争夺锁的问题
- 2.高内存占用
进程占用内存4GB (32bit os)
线程 4MB
我们可以让cpu无感 把一个内核线分为内核空间和用户空间,也就是在用户级区操作协程,用调度器来操作协程。也就是说把调度器做的越好,协程的效率也就更好
一个线程中可以有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。
go天生就支持协程,是因为go的底层做了协程调度器的转换。
golong对协程的处理
- 修改名字为goroutine
- 内存改为几kb,可以大量并发
- 灵活调度,也就是可以随意切换
golong的调度器处理
早期的调度器了只有一个全局的go协程队列
哪个需要就去拿,并且要获取锁释放锁,还会造成锁竞争。
如果这个协程创建了新的协程,可能回转移给其他线程来使用,就会导致局部性
GMP
改进之后
内核就是M 用户就是G 管理器就是p
一个p同时只能执行一个go
一个时间能够同行的go的数量就是p的数量
调度器的设计策略
复用线程
Work stealing机制(偷取机制):
全局队列里放的是放不下的G
Hand off机制(分离机制):
m1正在执行g1,m2即将执行g3,m1的g1阻塞了,就会创建/唤醒新的m3,把和m1绑定的p转换到m3,不耽误g2。如果g1还要工作就加入到其他m中,否则m1就睡眠或者销毁
利用并行
抢占
全局G队列(根据work stealing机制作为补充)
如果队列都没有g了,就去全局队列偷,每次偷也是需要加锁释放锁的机制,从而也会浪费时间
channel
channal有同步两个不同go的能力
一个协成在管道中取不到值就会阻塞 等待管道里面的值写 因为无缓冲的 (没设置情况)
基本使用
func TestChannl(t *testing.T) {
c := make(chan int, 3) //有缓存
go func() {
defer fmt.Println("goroutine exit")
for i := 0; i < cap(c); i++ {
c <- i
fmt.Println("goroutine send:", i)
fmt.Println("len:", len(c), "cap:", cap(c))
}
}()
for i := 0; i < cap(c); i++ {
fmt.Println("main等待===========")
fmt.Println(<-c) //接收打出来
}
fmt.Println("结束")
}
无缓冲
在第 1 步,两个 goroutine 都到达通道,但哪个都没有开始执⾏发送或者接收。
在第 2 步,左侧的 goroutine 将它的⼿伸进了通道,这模拟了向通道发送数据的⾏
为。这时,这个 goroutine 会在通道中被锁住,直到交换完成。
在第 3 步,右侧的 goroutine 将它的⼿放⼊通道,这模拟了从通道⾥接收数据。这
个 goroutine ⼀样也会在通道中被锁住,直到交换完成。
在第 4 步和第 5 步,进⾏交换,并最终,在第 6 步,两个 goroutine 都将它们的
⼿从通道⾥拿出来,这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在
都可以去做其他事情了。
有缓冲
在第 1 步,右侧的 goroutine 正在从通道接收⼀个值。
在第 2 步,右侧的这个 goroutine独⽴完成了接收值的动作,⽽左侧的
goroutine 正在发送⼀个新值到通道⾥。
在第 3 步,左侧的goroutine 还在向通道发送新值,⽽右侧的 goroutine 正在
从通道接收另外⼀个值。这个步骤⾥的两个操作既不是同步的,也不会互相阻
塞。
最后,在第 4 步,所有的发送和接收都完成,⽽通道⾥还有⼏个值,也有⼀些空
间可以存更多的值。
特点:
当channel已经满,再向⾥⾯写数据,就会阻塞
当channel为空,从⾥⾯取数据也会阻塞
关闭channel
channel不像⽂件⼀样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结
束range循环之类的,才去关闭channel;
关闭channel后,⽆法向channel 再发送数据(引发 panic 错误后导致接收⽴即返回零值);
关闭channel后,可以继续从channel接收数据;
对于nil channel,⽆论收发都会被阻塞。
select
单流程下⼀个go只能监控⼀个channel的状态,select可以完成
监控多个channel的状态
select具备多路channel的监控状态功能