redis的单线程
值拷贝与指针拷贝
//元类型(int string float bool ) 值拷贝:
string1:="i am charlie"
var string2 string
string2=string1
fmt.Printf("string1 :%v\n", string1)
fmt.Printf("string2 :%v\n", string2)
//string1 :i am charlie
//string2 :i am charlie
string1="i am bennie"
fmt.Printf("string1 :%v\n", string1)
fmt.Printf("string2 :%v\n", string2)
//string1 :i am bennie
//string2 :i am charlie
fmt.Println("+++++++++++++++++++++++++++++++++++++++++++++++++++++")
//指针类型(slice-(底层是array) 和 map-(底层hashmap) ),拷贝指针:
slice1:=make([]int,0)
slice1=append(slice1,1,2,3,4,5)
slice2:=make([]int,0)
slice2=slice1
fmt.Printf("slice1 :%v\n", slice1)
fmt.Printf("slice2 :%v\n", slice2)
//slice1 :[1 2 3 4 5]
//slice2 :[1 2 3 4 5]
slice1[0]=999
fmt.Printf("slice1 :%v\n", slice1)
fmt.Printf("slice2 :%v\n", slice2)
//slice1 :[999 2 3 4 5]
//slice2 :[999 2 3 4 5]
fmt.Println("+++++++++++++++++++++++++++++++++++++++++++++++++++++")
map1:=make(map[string]string)
map1["name"]="charlie"
map1["age"]="23"
map2:=make(map[string]string)
map2=map1
fmt.Printf("maP1 :%v\n", map1)
fmt.Printf("maP2 :%v\n", map2)
//maP1 :map[age:23 name:charlie]
//maP2 :map[age:23 name:charlie]
fmt.Printf("maP1 内存地址:%p\n", &map1)
fmt.Printf("maP2 内存地址:%p\n", &map2)
//maP1 内存地址:0xc000006030
//maP2 内存地址:0xc000006038
map1["name"]="bnnie"
fmt.Printf("maP1 :%v\n", map1)
fmt.Printf("maP2 :%v\n", map2)
// maP1 :map[age:23 name:bnnie]
// maP2 :map[age:23 name:bnnie]
fmt.Println("+++++++++++++++++++++++++++++++++++++++++++++++++++++")
//函数传参也是如此
fmt.Printf("after func slice1 :%v\n", slice1)
fmt.Printf("after func slice2 :%v\n", slice2)
//after func slice1 :[10000 2 3 4 5]
//after func slice2 :[10000 2 3 4 5]
fmt.Printf("after func maP1 :%v\n", map1)
fmt.Printf("after func maP2 :%v\n", map2)
//after func maP1 :map[age:23 name:bennie & charlie]
//after func maP2 :map[age:23 name:bennie & charlie]
// 结构体是值拷贝:
type struct1 struct{
Name string
Age int
}
var mystruct1 struct1
mystruct1.Name="charlie"
mystruct1.Age=23
var mystruct2 struct1
mystruct2=mystruct1
fmt.Printf("mystruct1 :%v\n", mystruct1)
fmt.Printf("mystruct2 :%v\n", mystruct2)
// mystruct1 :{charlie 23}
// mystruct2 :{charlie 23}
mystruct1.Name="bennie"
fmt.Printf("mystruct1 :%v\n", mystruct1)
fmt.Printf("mystruct2 :%v\n", mystruct2)
// mystruct1 :{bennie 23}
// mystruct2 :{charlie 23}
+++++++++++++++++++++++++++++++++++++++++++++++++
//函数传递是值拷贝到函数栈的参数空间,局部变量不改变
func changestruct(str struct1){
str.Name="bennie & charlie"
}
fmt.Printf("after func mystruct1 :%v\n", mystruct1)
fmt.Printf("after func mystruct2 :%v\n", mystruct2)
指针类型
a:= "charlie"
b:=&a
fmt.Printf("b的类型是:%T\n",b)
fmt.Printf("b存储的值是:%v\n",b)
fmt.Printf("b存储在: %p\n",&b)
fmt.Printf("对指针b取值: %v\n",*b)
fmt.Printf("对指针b取地址:%p\n",&b)
c:=b
fmt.Printf("c的类型是:%T\n",c)
fmt.Printf("c存储的值是:%v\n",c)
fmt.Printf("c存储在: %p\n",&c)
fmt.Printf("对指针c取值: %v\n",*c)
fmt.Printf("对指针c取地址:%p\n",&c)
b的类型是:*string
b存储的值是:0xc00003e250
b存储在: 0xc000006028
对指针b取值: charlie
对指针b取地址:0xc000006028
c的类型是:*string
c存储的值是:0xc00003e250
c存储在: 0xc000006038
对指针c取值: charlie
对指针c取地址:0xc000006038
*指针---是一种类型
& ----对一切取地址
*----对指针取值
位移运算
tf8编码
总结:
1 异或运算 相异为1,相同为0
2 位移是二进制整体向左或向右移动,左移后后面补0,右移移出格的位就抹掉
3 唯一相当于十进制的乘除运算
内存对齐
32位系统一次读取48bit=32位,64位系统一次读取88bit=64位二进制数据
规则:
1小于最大对齐边界区小于的值
2大于等于最大对其边界直接取8
结构体的对齐边界==结构体元素中最大的对齐边界
1 每个成员都是用同一个内存起始点作为地址0,通过相对地址类均订存储位置
2 相对地址与类型最大地址边界取模,得到存储位置
3 结构体整体占有字节数需是最大对其边界的倍数24%8=0,所以移到第24
为何要对齐到24,试想如果T组成list [2]T,那么程序会分配44字节的内存给list,那么list的第二个元素就以22开始存储,22&%8并不等于0,所以不能最为起始地址,
虚拟内存
线性地址
1虚拟内存又称线性地址
2是虚拟内存到物理内存的映射
1页目录为指针,指向存储物理内存地址的页表
32位下两级链表就可以映射4g的内存空间了
32位下:
4*8=32位
前10位定位页表地址
中间10位定位物理内存地址页
最后12 为通过偏移值就可以定位物理地址内存页上所有的地址了 (4k个地址)
进程隔离
每个进程都有自己对应的链表,既是现行地址相同,对应的物理地址也不一样没这样就实现了不同进程间的物理地址隔离
1 已映射的线性地址才是有效的地址
2 cpu访问物理内存也需要通过线性地址转换,由内存管理单元mmu完成,转换后存入缓存TLB,方便下次访问
3 切换进程,页目录就会改变,之前的TLB缓存就会失效,需要重新映射-缓存,这也是切换进程代价高的原因
4 cpu查链表发现对应物理内存也还咩有映射,就会pagg fault,cpu会查询pcb该进程是否申请,已申请的话就完成映射
race检测
继承/方法
1
,
值接收者方法
v
a
l
u
e
,不论嵌入的是值还是地址,都能拿到
\color{#A0A}{1,值接收者方法value,不论嵌入的是值还是地址,都能拿到}
1,值接收者方法value,不论嵌入的是值还是地址,都能拿到
2
,
指针接收者方法
s
e
t
,只有指针嵌入能拿到
\color{#A0A}{2,指针接收者方法set,只有指针嵌入能拿到}
2,指针接收者方法set,只有指针嵌入能拿到
make和new初始化
在
G
o
语言中,
s
l
i
c
e
,
c
h
a
n
n
e
l
,
m
a
p
这三个指针类型需要
m
a
k
e
初始化
\color{#A0A}{在Go语言中,slice,channel , map这三个指针类型 需要make初始化}
在Go语言中,slice,channel,map这三个指针类型需要make初始化
new:
iota
1 iota配合const使用
2 行数的索引,从0开始,在第几行iota就是几
3 常亮没有赋值的,上一行是iota,+1;上一行是int,=上一行的值
定义在同一行就是同一个index
结构体比较
sn1 := struct {
age int
name string
}{age: 11, name: "qq"}
sn2 := struct {
age int
name string
}{age: 11, name: "qq"}
if sn1 == sn2 {
fmt.Println("sn1 == sn2")
}
结构体只有在属性相同 类型相同 顺序相同才可以==,否则panuc
append多个元素
短变量声明
var(
size =1024
max_size = size*2
)
func main() {
size2:=65536
fmt.Println(size,max_size,size2)
}
只能在func内部,全局变量不能用:=
常量
常量不能取地址
自定义类型和别名
//一、类型别名 用=
type byte=uint8
type rune=int32
//二、区别类型别名和类型定义
// 自定义类型myInt,基本类型是int
type myInt int
//将 int 类型取一个别名intAlias
type intAlias = int
func main() {
var a myInt //声明 a变量为自定义 myInt 类型
fmt.Printf("a Type: %T, value: %d\n", a, a) // 输出 a 的类型和默认值
var b intAlias //声明 b变量为 intAlias 类型
fmt.Printf("b Type: %T, value: %d\n", b, b) // 输出 b 的类型和默认值
}
a Type: main.myInt, value: 0
b Type: int, value: 0
//从上面的结果我们可以看出:
//a 的类型是 main.myInt,表示main 包下定义的myInt 类型
//b 的类型是 int 。intAlias 类型只会在代码中存在,编译完成时,不会有 intAlias 类型``
```go
结构体和结构体别名
变量作用域
函数调用栈
1
函数栈帧
2 函数栈帧是函数执行时在栈上分配的一段内存,栈底叫栈基,栈顶叫栈指针,依次是:
调用者栈基
局部变量
返回值
参数
call和ret
1call指令只做两件事
1在调用函数时在,将下一条指令的地址入栈-返回地址
2 跳转到被调用函数的入口处-下一个函数的栈基
2被调用者通过栈指针和偏移找到下一个函数的变量 参数 返回值
1 程序执行时,先将栈基和栈指针加载到特定寄存器
2 指令指针存储下一条要执行的指令地址
3 go语言一次性加载最大的栈帧,执行时指令指针通过栈指针和偏移量来寻址
假设函数a在a1处调用b1处的函数b,bp和sp的位置如上图
1 call指令先将返回地址a2写入a栈帧,函数a栈帧的栈指针向下移8位到s4,更新sp的值
2 指令指针ip存入b1地址
开始执行b
1 入栈返回地址s1,确定栈基和栈指针,执行代码
2 编译器插入回复调用者A的栈基地址-s1
3 释放自己的栈帧空间 分配时向下移动了多少,就释放多少
b执行完就开始执行ret函数
1弹出返回地址s3
2跳转到这个地址
传参
参数入栈-从右到左,先入栈b 再入栈a
函数传参是值拷贝,因为 ∗ ∗ 交换的仅仅是参数的值 , 不是局部变量的值 ∗ ∗ \color{#A0A}{**交换的仅仅是参数的值,不是局部变量的值**} ∗∗交换的仅仅是参数的值,不是局部变量的值∗∗,swip函数执行失败
1 局部变量a,b
2 参数 a的地址,b的地址
运行时:
1,定位到参数 a的地址,b的地址
2,将这两块内存地址交换
返回值
main栈帧:
main栈基
局部变量a初始化为0
局部变量b=0
main的栈指针
incr栈帧:
返回地址
调用者栈基
局部变量b初始化为0
开始执行incr()
1a自增1, a=1
2将a的值赋给incr函数的局部变量b , b=1
3 到reture这里ret函数涉及到先retur你还是先defer– ∗ ∗ r e t u r n 先将返回值写入返回只值地址,再执行 d e f e r ∗ ∗ \color{#A0A}{**return先将返回值写入返回只值地址, 再执行defer**} ∗∗return先将返回值写入返回只值地址,再执行defer∗∗
4 执行return ,将b=1拷贝到返回值空间(返回给调用方)
执行defer函数
1 参数a的值+1(参数空间a的值+1)
2incr局部变量 b的值+1 (incr函数栈帧b的局部变量值+1=2)
3 main栈帧返回值空间b的值(1)赋给main的局部变量的b
最终返回0,1
mian有两个局部变量a,b,初始化为0,
一个参数a,初始化为0,
incr函数没有局部变量
1先执行a+1 参数a=1
2 返回值是a ,所以将参数a=1拷贝到返回值空间
执行defer函数
1 a+1
2 b+1
3 将返回值赋给main的局部变量b
dlv常用命令:
core可以查看具体goroutine的栈帧
元类型 自定义类型
1 类型元数据保存在runtime.type中
2 如果是自定义类型,还要有uncommontype结构体
别名共享一个runtime.type,自定义类型咋会生成不同的runtime.type结构体
方法
由上面我们知道,类型的方法由类型的元数据结构体可以找到
1,方法就是普通的函数
2,接收者就是隐含的第一个参数
传参
eg:
函数栈分布
1 局部变量是struct,数据存在堆的数据段,栈上只存局部变量的地址及siz
2 返回值空间
3参数,将局部变量拷贝过来
运行:
1 参数拼接,重新分配地址addr2,大小为8kb,参数变为addr2 8
2 返回,将参数拷贝到返回值空间
3 局部变量a是值接收者,通过它调用的方法时,修改的是拷贝过去的参数,局部变量a的值并未改变
传地址
将上面的函数改为传a的地址
1局部变量a为堆上的struct地址和siz 占16字节
2变量pa,值是a的地址
3 参数pa,是局部变量a的地址
1,步骤1要修改参数pa的变量&a,更新为addr2,局部变量a对应的地址也会更新
2,拷贝返回值是拷贝的是更新后的地址addr2
而在实际调用的时候,指针变量可以作为参数调用值拷贝的方法
值拷贝的参数也可以调用指针类型方法,编译器会自动转化传参
函数作为参数,返回值时以
f
u
n
c
v
a
l
方式存在
\color{#A0A}{函数作为参数,返回值时以funcval方式存在}
函数作为参数,返回值时以funcval方式存在
闭包就是有捕获列表的
f
u
n
c
v
a
l
\color{#A0A}{ 闭包就是有捕获列表的funcval}
闭包就是有捕获列表的funcval
f
u
n
c
v
a
l
的函数入口在堆代码段,数据段存捕获列表和
f
n
=
a
d
d
r
,栈上也只存入口地址
\color{#A0A}{ funcval 的函数入口在堆代码段,数据段存捕获列表和fn=addr,栈上也只存入口地址 }
funcval的函数入口在堆代码段,数据段存捕获列表和fn=addr,栈上也只存入口地址
方法表达式:
f2–方法变量
f3–接受getfnuc返回值,实际形成闭包,捕获变量a
接口
空接口
1,接受任意类型的数据*type
2 记录数据存储的位置 unsafe.pointer
1 定义e为空接口类型,未赋值之前type和date都为nil
2 将e赋值为os.file类型,type指向os.file ,data指向变量f的地址
3 类型元数据可以找到类型的方法
非空接口
1 非空接口要求实现接口的所有方法
2 接口的动态类型以及方法列表存储在itab结构体中
eg:
1定义一个io.readwrite类型的变量rw,未赋值前,tab和data为nil
2将os.file类型的变量f赋值给rw
3 tab会指向itab结构体,接口类型inter指向io.readwrite interface类型
4 itab的type指向os.file元数据
5 io.readwrite的方法会拷贝到itab的func列表
go语言把接口和itab缓存以key value形式存储在hash表,方便使用时快速获取
接口类型断言
空接口
语法:common,ok:=name.元类型
空接口—直接找type
上面断言ok
断言失败
空接口.非空接口–
1-要判断e的动态类型是否实现io.readwrite接口的方法
2 要找itab缓存是和否有元数据类型的方法列表
非空接口
通过itab的地址去找itab缓存,找对应的type
断言:
1判断对应的动态类型
2判断是否实现了对应的方法
reflect
因为runtime包未导出,reflect重新定义了这些方法
获取变量类型TypeOf()
type接受空接口,返回变量的类型信息
1,把emptyinterface包装成eface
2 type本身是一个非空接口,
typeof接收空接口,需要的是地址,这里就不能直接将局部变量a的值拷贝到参数空间
也不能用a的地址,因为不能改变局部变量的值
因此在编译阶段,go会增加一个临时变量作为a的拷贝,这样既符合空接口的地址,也不会直接作用到局部变量,所有传值是空接口的函数,都要这样处理来满足要求
修改变量
v.setstring指向的是临时变量逃逸到堆上的数据,这个数据实际是编译增加的临时数据,不是用户态的,所以没有任何意义,因此panic
1变量a逃逸到堆上,局部变量空间只存地址&a,
2参数包含a的地址&a和type指向的地址*stringtype
3将参数包装成eflect.value类型拷贝给局部变量v,加上flag
4将参数包装成eflect.value类型拷贝给返回值,加上flag
1调用v.elem函数,拿到v.ptr指向的变量a,并包装成reflect.value类型的返回值,更新参数v,更新返回值