包.xx
使用,这时包名就像Java的类名一样,导入包时就像java导入类一样;module
进行包管理,这种方式下有以下规则(规范)
-
、且和文件夹名相同%USERPROFILE%\go
基本命令
/*
download download modules to local cache (下载依赖的module到本地cache))
get 下载并编译
edit edit go.mod from tools or scripts (编辑go.mod文件)
graph print module requirement graph (打印模块依赖图))
init initialize new module in current directory (再当前文件夹下初始化一个新的module, 创建go.mod文件))
tidy add missing and remove unused modules (增加丢失的module,去掉未用的module)
vendor make vendored copy of dependencies (将依赖复制到vendor下)
verify verify dependencies have expected content (校验依赖)
why explain why packages or modules are needed (解释为什么需要依赖)
*/
//常用就是get、init、tidy
//例如初始化一个module:在cmd中运行:
go mod init "moduleName"
标识符【即变量】(和java一样:命名只能是数字字母下划线)的定义(三种)
//var
var a = 12
//:=
a := 12
//var+类型
var a int = 12
go中:首字母大写表示全局可见、首字母小写表示当前包可见(无论是源文件的变量、结构体的变量、方法或者函数
var B = 13//全局变量
//该结构体全局可见
type Out struct {
Name string //全局可见
password string //包可见
}
//全局可见
func (o *Out) setPassword(password string) {
o.password = password
}
//当前结构体包可见
type out struct {
password string //包可见
}
//当前包可见
func (o *out) show() {
}
var bb = 15//当前包可见
func main() {
var a = "aaaa"//局部变量
}
//当前包可见
func test1() string {
return "private 函数"
}
//全局可见
func Test2() string {
return "public 函数"
}
常量:和c一样通过const
定义、支持通过iota
实现常量计数器的功能(弥补了没有枚举的弊端)
const (
a = iota
b
c
d
)
//a=0,b=1,c=2……
指针:和C语言一样***、&
、有意思的是在go中常量是不允许取地址的**;这对于结构体赋值有非常大的影响例如
//全局常量
const CONST_NUM = "全局常量"
func main() {
const ccc = "内部常量"
var value = "string"
var find = &value
fmt.Println(*find==value)
}
//这时将不能直接给name通过取数值地址的方式赋值
type name struct {
aaa *string
}
func init() {
abc = "111111"
test := name{
// aaa : &"111"//这将不可行
aaa = &abc
}
}
输出:在go中输出和C语言基本一样,可以格式化输出(Printf
)、如果不指定格式化输出就使用原生输出(这时类似byte
会输出int8
,因为其通过int8
保存
func test(){
var a = "aaaa"
var b = 'b'
var c = 12
var d = false
fmt.Printf("%s----%c----%d----%t",a,b,c,d)
}
需要说明的是go通过内置函数来操作内置的复杂的数据类型(切片、map、channel),例如make、range等;有意思的是range遍历的时候获取到的是key(或者说下标)
标签:在go中为了方便进行json序列化和反序列(数据库字段别名等),可以给结构体的字段加上标签,然后在通过反射机制就可以实现变量名的转化
普通变量
byte
和rune
;其中byte
对应java的char、rune
对应的是utf-8的字符(即包含除了ASIC码以外的中英文),在go中本质保存的是int8和int32
特殊变量
complex64
和 complex128
:复数类型. ->
运算【主要是结构体非常特殊,必须支持运算】;类型
在go中允许通过type
类型定义和类型别名实现指代原生类型,和c的typedef一样
type NewInt int //定义类型NewInt,其保存方式和int一样
type IntAlias = int //给int其别名
//在go中,可以选择数字占用的位数
//无符号数和有符号数分类一样
//int、uint:默认占用机器字长的位数:64位机器占用64位、32位占用32位
var a1 int8 = 1 //var a1 uint8 = 1
var a2 int16 = 1
var a3 int32 = 1
var a4 int64 = 1
//浮点类型只有32位和64位(相当于float和double
var f float32 = 1.1
var d float64 = 1.1111111
//布尔
var b1 bool = false
var b2 = true
//字符
var c1 byte = 'a'
var c2 rune = '中'
//字符串
var s1 = "aaaaaa"
在go中允许通过make创建切片、map和channel这三种类型,但是不允许通过其创建其他类型
make的作用主要就是分配一个保存切片、map、channel信息的数据结构(结构体)
这三者是利用结构体实现,所以他们本身是复杂数据类型,可以认为就是Java中的对象一样
var slice1 = make([]type,length,capacity)
var map1 = make(map[type]type,capacity)
var channel = make(chan type,capacity)
字符串
切片
append(slice,元素,元素,元素【……】)
或者append(slice,slice1...)
cap(slice)
、len(slice)
:切片容量和长度copy(toSlipe,fromSlipe)
:在go中copy函数专门用于切片的copy,需要说明的是go中切片的copy本质就是元素复制,而且是通过覆盖的方式复制,所以
//数组:值得注意的是range的遍历获取到的是下标,而不是value
var nums = []int{1,2,3}
for v := range nums {
fmt.Println(nums[v])
}
var nums2 = [...][2]int{{1,2},{2,3}}
for v := range nums2 {
for vv := range nums2[v] {
fmt.Println(nums2[v][vv])
}
}
//切片
var slice1 = []int{}
slice1 = append(slice1,1,2,3,4,5,6)
fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>")
fmt.Printf("%d:",len(slice1))
//创建切片(足够大空间保存被复制)
var slice2 = make([]int,10,10)
//偏移2开始复制,返回复制量
copy(slice2[2:],slice1)
for v := range slice2 {
fmt.Print(slice2[v])
}
//可以通过追加切片的方式进行非覆盖的复制,相当于有足够容量下,偏移到切片的末尾的复制
slice2 = append(slice2,slice1...)
slice1[0] = 999
fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>")
for v := range slice2 {
fmt.Println(slice2[v])
}
key
都不会有返回值//map
func test1() {
var m1 = map[string]int8 {
"lili":12,
"leilei":13,
}
//增删改查
m1["lala"] = 111
fmt.Println(m1["lala"])
m1["lala"] = 122
fmt.Println(m1["lala"])
//有意思的是,delete没有返回值,所以无论删除存在都无法感知,需要辅助(例如先检查是否存在、检查删除前后map大小等)
delete(m1,"lala")
fmt.Println(m1["lala"])
//获取value和是否存在value
var mv1,has = m1["lala"]
if has {
fmt.Print(mv1)
}
for k1,v1 := range m1 {
fmt.Println(v1,k1)
}
fmt.Println(len(m1))
}
make
的时候可以自定义
select
机制避免一直阻塞
default
case
校验超时时间make
后就处于打开状态close
:立刻使得写入关闭【换句话说就是通道变为只读】,但是读取还是可读(通过检查读取会发现仍然是打开状态),当通道为空时才算关闭了通道byte
和rune
,这两者分别是int8
和int32
的类型别名,编译后本质保存的是int8或者int32
结构体.和结构体指针->
两种访问内部属性方式),数据和程序分离,其c又略微不同;在go中有面向对象的思想的支持,后面重点介绍//
type People struct {
id int32
name string
address string
}
type student struct {
school string
class string
people People //组合
People //匿名的方式实现
}
type worker struct {
city string
work string
people People
}
//是worker的成员方法,绑定worker
func (w worker) show() {
w.city = "bbbbb" //这里传入的是结构体的浅拷贝,所以普通数据类型修改后,返回后等于没有修改
fmt.Printf("%v",w)
}
//是Student的成员方法,绑定Student
func (s *student) show() {
s.school = "aaaaa" //这里传入的是结构体指针所以修改在退出方法后仍然有效
fmt.Printf("%v",s)
}
//是People的成员方法,绑定People
func (p People) show() {
fmt.Printf("%v",p)
}
func main() {
var t worker
var p People
t.show()
p.show()
}
接口和组合
go中有组合和匿名结构体的概念【go认为组合优于继承、go通过组合实现复用】,通过匿名结构体继承该结构体的所有属性和方法,需要说明的是这种组合的情况下不会有继承带来的多态的概念(就是不会有方法覆盖)
嵌套匿名结构体(模拟继承)
嵌套有名结构体(组合)
type peoples struct {
name string
age int
}
type student struct {
//嵌套匿名结构体
people
class string
}
type worker struct {
//嵌套有名结构体
p people
city string
}
func main() {
var w = worker
var s = student
w.name = "1111"//直接当做当前结构体属性使用,
s.p.name = "1111"//必须指定使用
}
go的接口非常重要,由于go不支持继承、这时需要一种机制来实现扩展性,这种机制就是接口,有意思的是在go中接口居然不需要显示的实现
面向对象编程中多态是非常重要的功能,多态使得程序的复用和解耦合得以实现,但是多态是继承带来的优点,go只能组合;考虑到多态本质就是持有持有该类型方法的引用地址进行调用,在go中可以通过公共结构体(复杂数据类型)的interface实现多态;
package main
import "fmt"
type people interface {
showId()
}
type cus struct {
id string
people
}
func (c cus) input() {
fmt.Printf("id:%s",c.id)
c.showId()
}
type older struct {
likes string
}
type young struct {
likes string
}
func (y young) showId() {
fmt.Printf("young like:%s",y.likes)
}
func (o older) showId() {
fmt.Printf("ole like:%s",o.likes)
}
func main() {
var c1 = cus{"1111",young{"eat"}}
var c2 = cus{"2222",older{"sleep"}}
c1.input()
c2.input()
}
所以在go中接口不仅仅是方法的抽象,而且所有复杂数据类型和结构体都默认实现了空接口,换句话说就是空接口类型时所有类型的父类型,类似java的Object
通过断言可以将空接口转化为具体类型,一定程度上弥补了没有继承带来的问题
//例如数组
var c = [...]int{1,2,3}
var i interface{}
i = c
s := i.([3]int)
fmt.Printf("%v,%d",c,len(s))
//切片
var c1 = []int
var i1 interface{}
c1 = append(c1,11)
i1 = c1
s1 := i1.([]int)
fmt.Printf("%v,%d",c,len(s))
go支持反射,所以在结构体中有tag
的概念,可以通过反射获取tag
,以方便操作结构体的属性
支持方法,不过go的方法稍微有点区别
go的结构体支持成员方法,这一点将和c完全不同,使得结构体具有一定的对象的性质(但是还是要说,go中,结构体是具有基本数据类型性质的);
在go的语法糖中默认实现了结构体所有字段的构造方法,即通过类似json赋值一样对任意字段进行复制初始化结构体;
go的成员方法也是通过是否为首字母大小写确定权限范围(大写相当于public、小写相当于protect:包内可以访问)
**go的结构体中没有隐式变量(this)的概念,通过和结构体绑定的成员方法自定义this;**这个绑定就是所谓的接受者,接受者类型决定了是否需要浅拷贝数据(普通类型)还是拷贝引用(指针)
需要说明的是go的方法相当于java的static方法,所以在go中的方法如果传入(绑定)的是普通结构体而不是结构体指针的话,修改返回后将不生效;
在go中会自动解引用,换句话说就是传入结构体指针的使用方式可以当做结构体直接使用
type peoples struct {
name string
age int16
hobby string
}
func (p peoples) show1() {
p.name = "123"
fmt.Printf("%v\n", p)
}
func (p *peoples) show2() {
//(*p),name = "123" //自动解引用后两者效果一样
p.name = "123"
fmt.Printf("%v\n", *p)
}
func main() {
/**
var peo = peoples {
name : "lili",
}
var peo = peoples {
"lili",12,"eat",
}
*/
var peo = peoples{name:"lili"}
//(&peo).show1(),这里在go的语法糖中会自动解析为peo.show1()
peo.show1()
//注意这里自动由于自动解引用,和(&peo).show2()效果是一样
peo.show2()
}
interface{}
interface{}
作为类型Object
一样,作为所有结构体祖先default方法、常量
implement
func (p *people) test() {}
假设这个接口是接口a
定义的接口,那么这个接口的实现类是*people
不是people
type iface interface {
eat()
}
type Integer int
func (i *Integer) eat() {
}
func main() {
var a A//接口类型A
var peopel = People{"aaaa"}
a = people//假设People实现了A接口
var peo People
peo,ok := a.(People)//类型断言,将a向下转为People类型
if ok {
//
}
}
go的函数允许多返回值(一般只用在返回错误的时候使用)
在go中函数就相当于与面向对象的类,是一级公民;
闭包=函数+环境
,这点和Java的内部类有很大的不同,在Java中只有final变量才能在匿名内部类使用(本质就是扩展生命周期,但是为了一致性不允许修改,表现为浅拷贝),在go中函数是完全支持闭包的,换句话说就是共享环境是可以动态修改的且作用域是在可以被引用的整个生命周期go严格规范了代码,所以对于有返回值的函数、无论有多少个返回值都必须接受,前面说到,定义的变量又必须使用,为了解决这里的矛盾(部分返回值可能用不到)可以使用_
接受即可
go函数和java方法一样支持变长参数、例如func add(nums...int) int {}
,这时nums数组长度由变长函数输入的个数确定
go的函数可以给返回值指定名称(绑定名称),这是因为go存在defer机制
,可以在defer中修改具名返回值的结果
在go中是:return分为两步(这点和Java类似):
在go中:defer可以通过修改具名返回值变量修改返回值
//结果为1,defer修改了a
func test() (a int) {
defer func() {
a++
}()
return 0
}
在go中传递是浅拷贝的传递,这点和java一样(但是需要注意结构体,因为在go中结构体是非常特别的,他作为基本数据类型,相当于java浅拷贝重写clone,使得拷贝的是结构体内所有的数据形成新的拷贝(而不是拷贝结构体指针))
每个源文件的执行顺序都是:初始化变量(及初始化变量调用的函数)->init函数->main函数->被调用函数
,这一点和java基本一致,不同在于
()
包括{}
符合换行规范while
、而且没有三元运算for
允许无条件或者指定为true,这种情况下相当于 java的for(;;)
;除此之外,go中还有一点就是,类似Java,允许迭代器变量(range
)switch
也很有意思(和其他语言不同),不需要break
,默认自动会break,如果需要继续使用的是fallthrough
表示继续下一条语句;和其他语言一样支持变量匹配和表达式匹配**if、if else
**和其他语言基本一样,除了没有括号break和continue
defer
,这样这条语句就是一个final内的语句,无论是否会发生异常都会被执行,另外defer
语句是逆序执行,极大的方便了代码的编写,但是遗憾的是final
同样不能指定域,只能是函数/方法的域,因此一旦进入final就会进入退出函数的流程;panic
相当于throw、recover
:相当于catch、不过遗憾的是go中没有try-catch、这就使得一旦panic抛出异常将会直接进入到达defer(即final)、为了实现catch需在其中设置recover中进行异常捕获,由于defer的设置,所以使用go的异常抛出时需要谨慎,因为这意味着下面的逻辑代码将不再被执行,而值返回的形式就可以逻辑判断再处理func printHello() {
defer func() {
err := recover()
if err != nil {
fmt.Println(err)
}
}()
//do假设这个函数抛出panic异常,就会被recover捕获;显然如果异常不处理就会终止程序
}
格式化输出表(Printf)
内建函数
函数名 | 使用 | 说明 |
---|---|---|
delete | delete(map,元素) | 删除map的元素,没有返回值,需要自主确认是否删除成功 |
append | append(切片,切片…) 切片/append(切片,若干元素) 切片 | 用在切片的添加 |
len | len(数组\切片\map\channel) int | 元素的数量 |
cap | cap(切片\channel) int | 容量 |
close | close(channel) | 关闭通道 |
make | make(type,len,cap) type | 创建切片、map、channel |
new | new(type) type | 分配内存,但是不会初始化该内存 |
complex | ||
imag | ||
real | ||
Unsafe.Sizeof | Unsafe.Sizeof(type) | 获取大小 |
Unsafe.OffSet | ||
Unsafe.Alignof | ||
panic和recover | ||
print和printf |
defer
defer func
】可以说是go的一个亮点,并且go优化后的defer实现,使得defer几乎没有多大的成本;前面大概介绍了defer作用就是相当于final,而且是FIFO的_defer
保存执行需要的信息
goroutine
的defer调用链的首部go和select
<-
接受者自动解引用和引用
在结构体中非常有意思的是对于指针的保护
type testInterface interface {
test()
}
type testInterface1 interface {
test1()
}
type test11 struct {
name string
}
//指针类型接受者
func (t *test11) test() {
fmt.Println(t)
t.name = "lili2"
}
//值类型接受者
func (t test11) test1() {
fmt.Println(t)
t.name = "lili1"
}
func main() {
t := test11{name: "lilei",}
var f1 testInterface
var f2 testInterface1
f1 = &t//必须是指针类型
f2 = t //等价于f2 = t
f1.test()
f2.test1()
}
go 函数
,并且使用的是方法,这使得协程非常轻量级go
开启的其他协程都是以从协程进行select - case - default
机制
default
时则为阻塞等待,必须等到有一个通道可以读/写break
,所以选择一个case
后就不会继续选择case/default
default
,那么每次轮询都会有选择(当没有case选择时就会选择default)var channel = make(chan string,3)
func hello(name string) {
channel <- name
fmt.Println("hello", name)
}
func bye() {
name := <-channel
fmt.Println("bye", name)
}
func main() {
go hello("lili")
go bye()
time.Sleep(1111)
close(channel)
}
XXX_test + testing + go test
:go的单元测试框架XXX_test
testXxxxx
//假设这是一个abc_test
func testName(t *testing.T) {
//测试的函数
res := name()
if res== `a` {
t.Fatalf("执行错误")
}
t.Logs("执行成功")
}
//在文件目录执行go test
由于go没有类似Java的Class
这种入口,在go中反射是通过每个interface
持有的(value,type)
对实现的
type
是一个接口,有17种实现类(即17个type类型)
value
type
外,还有一个unsafe.Pointer
T
// TB is the interface common to T, B, and F.
type TB interface {
Cleanup(func())
//错误
Error(args ...any)
Errorf(format string, args ...any)
Fail()
FailNow()
Failed() bool
//停止
Fatal(args ...any)
Fatalf(format string, args ...any)
Helper()
//日志
Log(args ...any)
Logf(format string, args ...any)
Name() string
Setenv(key, value string)
Skip(args ...any)
SkipNow()
Skipf(format string, args ...any)
Skipped() bool
TempDir() string
// A private method to prevent users implementing the
// interface and so future additions to it will not
// violate Go 1 compatibility.
private()
}
在go中socket编程省略了bind
,服务器再listen
就会直接执行bind+listen
两步操作
socket编程主要:原生socket连接操作
和Conn
的后处理操作
socket操作:Dail、Listen、Accept
Conn操作:
主要是Read、Write、Close
type Conn interface {
//读写
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
//关闭
Close() error
//连接信息
LocalAddr() Addr
RemoteAddr() Addr
//短连接的读写超时(想秒后主动关闭连接)
//相当于延迟Close
SetDeadline(t time.Time) error
//变为半连接,主动发起关闭
SetReadDeadline(t time.Time) error
//变为不再发送
SetWriteDeadline(t time.Time) error
}
SetKeepLive
设置心跳的;goroutine
和channel即可快速实现ServeMux 是一个 HTTP 请求多路复用器。它将每个传入请求的 URL 与已注册模式列表进行匹配,并为与 URL 最匹配的模式调用处理程序。
模式名称固定,有根路径,如“/favicon.ico”,或有根子树,如“/images/”(注意尾部斜杠)。较长的模式优先于较短的模式,因此如果同时为“/images/”和“/images/thumbnails/”注册了处理程序,则会为以“/images/thumbnails/”开头的路径调用后一个处理程序,而前者将接收对“/images/”子树中任何其他路径的请求。
请注意,由于以斜杠结尾的模式命名了根子树,因此模式“/”匹配所有其他注册模式不匹配的路径,而不仅仅是具有 Path ==“/”的 URL。
如果已注册子树并且接收到命名子树根但没有尾部斜杠的请求,则 ServeMux 将该请求重定向到子树根(添加尾部斜杠)。可以通过单独注册不带斜杠的路径来覆盖此行为。例如,注册“/images/”会导致 ServeMux 将对“/images”的请求重定向到“/images/”,除非“/images”已单独注册。
模式可以选择以主机名开头,将匹配限制在该主机上的 URL。特定于主机的模式优先于一般模式,因此处理程序可能会注册两种模式“/codesearch”和“codesearch.google.com/”,而不会同时接管对“ http://www.google.com/ ”的请求”。
ServeMux 还负责清理 URL 请求路径和 Host 标头,剥离端口号并重定向任何包含 .或 .. 元素或重复斜杠到等效的、更清晰的 URL。
ServeMux 还负责清理 URL 请求路径和 Host 标头,剥离端口号并重定向任何包含 .或 .. 元素或重复斜杠到等效的、更清晰的 URL。
func main() {
//绑定路由
http.HandleFunc("/hello", helloHandler)
//绑定端口,nil表示使用默认的http处理
err := http.ListenAndServe("127.0.0.1:8999", nil)
if err != nil {
fmt.Println("检查输入")
}
}
//处理函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
var res = "hello world"
_, err := w.Write([]byte(res))
if err != nil {
fmt.Println("输出有误")
}
}
Pointer的机制
强制类型转换(跳过go安全检查)Pointer是unsafe的自定义类型
type Pointer *ArbitraryType
,该类型底层就是一个ArbitraryType类型的地址,任意类型地址Pointer 表示指向任意类型的指针。 Pointer 类型有四种特殊操作可用:
因此,指针允许程序破坏类型系统并读写任意内存。使用时应格外小心。以下涉及 Pointer 的模式是有效的。不使用这些模式的代码很可能在今天无效或在未来变得无效。
(1) 将 T1 转换为指向 T2 的指针。假设 **T2 不大于 T1 并且两者共享相同的内存布局,则此转换允许将一种类型的数据重新解释为另一种类型的数据。**一个例子是 math.Float64bits 的实现:
func Float64bits(f float64) uint64 {
return (uint64)(unsafe.Pointer(&f))
}
(2) 将指针转换为 uintptr(但不返回指针):将指针转换为 uintptr 会生成指向值的内存地址,作为整数。这个时候:
(3)允许使用地址运算将指针转换为 uintptr 然后通过Pointer获取地址。(例子如下),但是
运算只能在一个表达式中;
不允许通过对nil
进行地址运算;
不允许通过地址运算获取分配的末尾;
例子
// 结构体地址运算 f := unsafe.Pointer(&s.f)
f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))
// 数组地址运算 e := unsafe.Pointer(&x[i])
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))
//错误的地址运算
// 将地址移到内存末尾
var s thing
end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))
b := make([]byte, n)
end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))
//不再一个表达式/对象为nil
u := unsafe.Pointer(nil)
p := unsafe.Pointer(uintptr(u) + offset)
(4) 调用 syscall.Syscall 时将指针转换为 uintptr。(还未学习,略)
(5) 反射包下的地址/地址数值转换运算。尽管是后兼任的,但是也要符合unsafe
包的地址运算规则;
(6) reflect.SliceHeader 或 reflect.StringHeader 数据字段与指针的转换。(见下文反射实现)
ArbitraryType
和IntegerType
,底层都是int;
type ArbitraryType int
:任意类型type IntegerType int
:任意整数类型Sizeof
:变量占用的内存大小Offsetof
:获取结构体/切片/数组中某个元素的偏移量Aligonf
:获取对齐值Add
:地址运算,等价于unsafe.Pointer(uintptr(unsafe.Pointer(&xxxxx)) + unsafe.Offsetof(XXXXX))
Slice
:获取一个切片,其底层数组从 ptr 开始,长度和容量为 len。unsafeheader
包,这个包尽管不再unsafebao,但是也是一个unsafe操作的包;这里涉及到切片和string的底层实现unsafe.Pointer
保存数据的地址,通过Len
控制访问的空间(可以偏移的大小)type Slice struct {
Data unsafe.Pointer
Len int
Cap int
}
type String struct {
Data unsafe.Pointer
Len int
}
time
包时go的标椎时间包sync
:同步机制sync/atomic
:原子变量context
:上下文channel
:协程通信GC
,除了极可能栈上分配外就是对象池技术Value
:相当于原子变量,可以装载any
(任意类型)的变量;
退出通知
、元数据传递
的功能。
context
使得他们具有关系(Context将可以具有父子关系),而且context线程安全(并发安全),所以可以将一个context传入多个goroutine
;channel+select
机制控制本质不是用来控制协程关系的(是一个协程的通信机制);ctx
。 context.todo
。package main
import (
"context"
"fmt"
)
func main() {
//根context
ctx := context.Background()
//获取一个可以传递参数的context
valCtx := context.WithValue(ctx, "id", "333333")
//传递context
go value(valCtx)
//获取一个超时取消的Context,假设10秒后自动取消
timeCtx, cancelFunc := context.WithTimeout(ctx, time.Second*10)
defer func() {
//假设30秒后有人掉线
time.Sleep(time.Second*15*2)
cancelFunc()
fmt.Println("final")
}()
go timeOut(timeCtx)
}
func value(ctx context.Context) {
id, ok := ctx.Value("id").(string)
if ok {
fmt.Printf(id)
} else {
fmt.Printf("no id")
}
}
//假设这个超时超时取消任务是:两人联机,每秒获取两人的位置,再想双方发送位置
//获取方式是
//超时获取:即对局内有效,对局结束自动关闭
//可以取消;当连接断开时不再获取,退出
func timeOut(ctx context.Context) {
for {
getStand()
sendStand()
select {
case <-ctx.Done(): {
return
}
case <-time.After(time.Second):
}
}
}
Value
、Done
、Deadline
、Err
Value
:获取 key 对应的 value,可以通过Context在goroutine将传递数据Done
:当 context 被取消或者到了 deadline,返回一个被关闭的 channelDeadline
:返回 context 是否会被取消以及自动取消时间Err
:在 channel Done 关闭后,返回 context 取消原因cancel
:cancel方法通知后续创建的goroutine退出Done
:返回chan,后端goroutine可以通过监听确定是否需要退出emptyCtx
:空的Context,一般用于占位或者标记为根context
,下面两种方法生成改类型context
Background
:一般根的Context可以通过这种方法生成TODO
:占位,当前没有context需要传递到函数中,但是 不能传递nil,所以使用TODO生成占位的ContextcancelCtx
:可以取消的context
WithCancel
timerCtx
:定时取消的context
WithTimeout
valueCtx
:可以携带数据的context,数据通过key value
键值对形式传入
WithValue
:推荐使用结构体/接口类型的value,而且key必须是可以比较的实现
valueCtx
和timerCtx
&cancelCtx
type timerCtx struct {
//继承cancelCtx
cancelCtx
timer *time.Timer //定时器,看前文
deadline time.Time//截止时间
}
type cancelCtx struct {
Context
mu sync.Mutex // 同步锁,前文
done atomic.Value // chan struct{},懒惰地创建,由第一次取消调用关闭
children map[canceler]struct{} // 在第一次取消调用时设置为 nil
err error // 由第一次取消调用设置为非零
}
//只有key、value
type valueCtx struct {
Context
key, val any
}
对于context包,整体代码非常简介,下面主要学习两个生成context的方法
WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
//d:进入的时候的时间
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
//...
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
后面
json序列化:Marshal方法:序列化;Unmarshal:反序列化
func mian() {
marshal, err := json.Marshal(peo)
err = json.Unmarshal([]byte(marshal), &peo)
}
文件操作:和Java的基本一样,通过一个File操作文件
打开方式(和C语言差不多)
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 // 如果可能,打开时清空文件
)
Reader
io工具包
TOKEN序列
make、new、select
等替换为runtime
包下真正实现该功能的函数
makechan
<-
替换为runtime下chan下的相应函数和AQS一样,本质就是将线程状态在用户态模拟实现,所以主要有以下状态:可执行、执行、阻塞(等待)、死亡
在go中goroutine非常轻量级,只需要通过go调用函数即可开启,goroutine复用可以实现协程池的概念;
go中创建goroutine过程:
goroutine
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
//第一阶段:检查辅助gc
var assistG *g
if gcBlackenEnabled != 0 {
assistG = getg()
if assistG.m.curg != nil {
assistG = assistG.m.curg
}
// 积累信用
assistG.gcAssistBytes -= int64(size)
//欠债必须辅助gc
if assistG.gcAssistBytes < 0 {
gcAssistAlloc(assistG)
}
}
// Set mp.mallocing to keep from being preempted by GC.
mp := acquirem()
if mp.mallocing != 0 {
throw("malloc deadlock")
}
if mp.gsignal == getg() {
throw("malloc during signal")
}
mp.mallocing = 1
shouldhelpgc := false
dataSize := userSize
c := getMCache(mp)
if c == nil {
throw("mallocgc called without a P or outside bootstrapping")
}
var span *mspan
var x unsafe.Pointer
noscan := typ == nil || typ.ptrdata == 0
// 分配空间
delayedZeroing := false
//小于32k
if size <= maxSmallSize {
//小于16B
if noscan && size < maxTinySize {
//还有足够空间,直接在mcache分配
if off+size <= maxTinySize && c.tiny != 0 {
x = unsafe.Pointer(c.tiny + off)
c.tinyoffset = off + size
c.tinyAllocs++
mp.mallocing = 0
releasem(mp)
return x
}
// 否则再申请一个新的mcache,然后进行分配
span = c.alloc[tinySpanClass]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(tinySpanClass)
}
x = unsafe.Pointer(v)
(*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0
if !raceenabled && (size < c.tinyoffset || c.tiny == 0) {
c.tiny = uintptr(x)
c.tinyoffset = size
}
size = maxTinySize
//全局缓存分配
} else {
var sizeclass uint8
if size <= smallSizeMax-8 {
sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
} else {
sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
}
size = uintptr(class_to_size[sizeclass])
spc := makeSpanClass(sizeclass, noscan)
span = c.alloc[spc]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(spc)
}
x = unsafe.Pointer(v)
if needzero && span.needzero != 0 {
memclrNoHeapPointers(unsafe.Pointer(v), size)
}
}
//堆中分配
} else {
shouldhelpgc = true
span = c.allocLarge(size, noscan)
span.freeindex = 1
span.allocCount = 1
size = span.elemsize
x = unsafe.Pointer(span.base())
if needzero && span.needzero != 0 {
if noscan {
delayedZeroing = true
} else {
memclrNoHeapPointers(x, size)
}
}
}
var scanSize uintptr
//步骤三:标识位图
if !noscan {
heapBitsSetType(uintptr(x), size, dataSize, typ)
if dataSize > typ.size {
if typ.ptrdata != 0 {
scanSize = dataSize - typ.size + typ.ptrdata
}
} else {
scanSize = typ.ptrdata
}
c.scanAlloc += scanSize
}
//收尾工作
//检查是否为增量更新的gc需要添加扫描节点
publicationBarrier()
if rate := MemProfileRate; rate > 0 {
if rate != 1 && size < c.nextSample {
c.nextSample -= size
} else {
profilealloc(mp, x, size)
}
}
mp.mallocing = 0
releasem(mp)
//检查是否触发GC
if shouldhelpgc {
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}
return x
}
RememberSet
PS:后续所有开源学习笔记同步到gitee,有需要去拉取 https://gitee.com/wusport/open-source-notes