第一个Go程序
package main //包,表明代码所在的模块(包)
import "fmt" //引入代码依赖
//功能实现
func main(){
fmt.Println("Hello World");
}
编译执行
➜ go-example git:(master) ✗ cd src/example/main
➜ main git:(master) ✗ ls
hello_world.go
➜ main git:(master) ✗ go run hello_world.go
Hello World
➜ main git:(master) ✗ go build hello_world.go
➜ main git:(master) ✗ ls
hello_world hello_world.go
➜ main git:(master) ✗ ./hello_world
Hello World
应用程序入口:
- 必须是main包:package main
- 必须是main方法:func main()
- 文件名不一定是 main.go
GO单元测试
在 Go 语言中,标准库中提供了一个非常好用的单元测试工具包 testing 。
单元测试文件必须命名为 *_test.go,测试函数必须以 Test 为开头,接着是函数名,函数的参数为 *testing.T 对象。
package unit
func Square(op int) int {
return op*op
}
package unit_test
import (
unit "sample-app/src/example/test/unit_test"
"testing"
)
func TestSquare(t *testing.T) {
inputs := [...]int{1, 2, 3}
expected := [...]int{1, 4, 9}
for i := 0; i < len(inputs); i++ {
ret := unit.Square(inputs[i])
if ret != expected[i] {
t.Errorf("input is %d,the expected id %d,the actual %d", inputs[i], expected[i], ret)
}
}
}
数据类型
- bool
- string
- int int8 int16 int32 int64
- uint uint8 uint16 uint32 uint64 uintptr
- byte
- rune
- float32 float64
- Completx64 completx128
go 语言对类型转换十分严格
-
go 语言不支持隐式类型转换
-
别名和原有类型也不能进行隐式类型转换
运算符
Go 语言支持多种运算符,包括算术运算符、赋值运算符、比较运算符、逻辑运算符等
算术运算符
- + :加法运算
- - :减法运算
- * :乘法运算
- / :除法运算
- % :取模运算(求余数)
a := 6
b := 4
c := a + b
d := a - b
e := a * b
f := a / b
g := a % b
fmt.Println(c, d, e, f, g) // 输出 10 2 24 1 2
赋值运算符
= :将右侧的数值或表达式赋值给左侧的变量
a := 10
b := 5
a = a + b // a 的值变为 15
比较运算符
比较运算符通常用于控制程序的流程或进行条件判断
- == :判断两个值是否相等,相等则返回 true,否则返回 false
- != :判断两个值是否不相等,不相等则返回 true,否则返回 false
- < :判断左侧的值是否小于右侧的值,小于则返回 true,否则返回 false
- > :判断左侧的值是否大于右侧的值,大于则返回 true,否则返回 false
- <= :判断左侧的值是否小于等于右侧的值,小于等于则返回 true,否则返回 false
- >= :判断左侧的值是否大于等于右侧的值,大于等于则返回 true,否则返回 false
逻辑运算符
- && :逻辑与运算,左右两侧的表达式都为真,则返回 true,否则返回 false
- || :逻辑或运算,左右两侧的表达式有一个为真,则返回 true,否则返回 false
- ! :逻辑非运算,对右侧的表达式取反
条件和循环
在 Go 语言中,条件语句和循环语句都是基本的控制流语句。条件语句包括 if 语句、switch 语句,循环语句包括 for 语句。
循环
Go 语言仅支持循环关键字 for
package loop_test
import "testing"
// go 循环
func TestLoop(t *testing.T) {
//条件循环
n := 0
for n < 5 {
t.Log(n)
n++
}
// for range 循环
for index, value := range slice {
// 这里是循环体
}
// while 循环
for condition {
// 这里是循环体
}
//无限循环 this loop will spin, using 100% CPU (SA5002)
for {
//...
}
}
示例
for i := 0; i < 10; i++ {
fmt.Print(i, " ")
}
// for range 循环
fruits := []string{"apple", "banana", "cherry"}
for i, fruit := range fruits {
fmt.Printf("fruits[%d] = %s\n", i, fruit)
}
// while 循环
i := 0
for i < 5 {
fmt.Print(i, " ")
i++
}
if条件
//condition 表达式结果必须为布尔值
if condition {
}else if condition{
}else if condition{
}else{
}
var a int = 10
if a < 20 {
fmt.Println("a 小于 20")
} else {
fmt.Println("a 大于等于 20")
}
//支持变量赋值
if var declaration; condition{
}
switch 条件
package condition_test
import (
"runtime"
"testing"
)
func TestSwitchCase(t *testing.T) {
switch os := runtime.GOOS; os {
case "darwin":
t.Log("OS X.")
case "linux":
t.Log("Linux.")
default:
t.Log("%S.", os)
}
}
func TestSwitchMulitCase(t *testing.T) {
for i := 0; i < 5; i++ {
switch i {
case 0, 2:
t.Log("Even")
case 1, 3:
t.Log("Odd")
default:
t.Log("it is not 0-3")
}
}
}
func TestSwitchCaseCondition(t *testing.T) {
for i := 0; i < 5; i++ {
switch {
case i%2 == 0:
t.Log("Even")
case i%2 == 1:
t.Log("Odd")
default:
t.Log("unknow")
}
}
}
数组与切片
数组的声明
var a [3]int //声明并初始化为默认零值
a[0] = 1
b := [3]int{1,2,3} //声明同时初始化
c := [2][2]int{{1,2},{3,4}} //多维数组初始化
数组截取
a[开始索引(包含),结束索引(不包含)]
a := [...]int{1,2,3,4,5,6,7,8}
a[1:2] //2
a[1:3] //2,3
a[1:len(a)] //2,3,4,5,6,7,8
a[1:] //2,3,4,5,6,7,8
a[:3] //1,2,3
切片的初始化
切片只能和nil比较
// go 切片
func TestSliceInit(t *testing.T) {
var s0 []int
t.Log(s0, len(s0), cap(s0))
s0 = append(s0, 1) //添加参数
t.Log(s0, len(s0), cap(s0))
s1 := []int{1, 2, 3, 4}
t.Log(s1, len(s1), cap(s1))
s2 := make([]int, 3, 5) //长度为3 容量为5
t.Log(s2, len(s2), cap(s2))
t.Log(s2[0], s2[1], s2[2])
s2 = append(s2, 666)
t.Log(s2[0], s2[1], s2[2], s2[3])
}
数组和切片的区别
- 长度不同:数组的长度是固定的,切片的长度可以随时改变
- 内存管理不同:数组的内存大小在声明时已经确定,切片内部使用了动态数组的功能,底层会自动进行内存分配和扩容
- 传递方式不同:数组在函数中传递会进行一次值拷贝,而切片则传递一个指向底层数组的指针
Map
Map的初始化
m := map[string]int{"one":1,"two":2,"three":3}
m1 := map[string]int{}
n1["one"] := 1
m2 := make(map[string]int,10 /**init capactiy **/)
//Map的value可以是一个方法
m := map[int]func(op int)int{}
m[1] = func(op int) int {return op}
Map遍历
m := map[string]int{"one": 1, "two": 2, "three": 3}
for key, value := range m {
t.Log(key, value)
}
string
- string 是数据类型,不是引用类型和指针类型
- string 是只读的 byte slice
- string 的byte数组可以存放任何数据
Go语言中*和&的区别
*和&是 Go 语言中用于操作指针的运算符
- * 运算符被用于指针变量,可以访问指针地址中存储的值。
- & 运算符被用于获取变量的内存地址,生成指向该变量的指针。
函数
函数是一等公民
- 可以有多个返回值
- 所有参数都是值传递:slice,map,channel会有引用传递的错觉
- 函数可以作为变量的值
- 函数可以作为参数和返回值
//go多个返回值方法定义
func retrunMulitValues() (int, int) {
return rand.Int(), rand.Int()
}
func TestFn(t *testing.T) {
a, b := retrunMulitValues()
t.Log(a, b)
c,_ := retrunMulitValues()
t.Log(c)
}
可变参数
func TestVarParam(t *testing.T){
t.Log(sum(1,1,1,1,1))
}
func sum(ops ...int) int {
ret := 0
for _,item := range ops{
ret = ret + item
}
return ret;
}
defer 函数
Go 中的 defer 关键字用于在函数退出时执行一个函数或方法。defer 函数可以是一个普通函数、匿名函数或方法,它们的执行顺序与声明顺序相反,即后声明的 defer 函数先执行。可以用来释放资源、记录日志、错误处理等操作
defer function_name(params)
func Clear(){
fmt.Println("clear resources!")
}
//defer 函数
func TestDefer(t *testing.T){
defer Clear()
t.Log("start")
t.Log("execute")
}
Go行为的定义和实现
封装数据和行为
结构体定义
type Employee struct{
Id string
Name string
Age int
}
实例创建及初始化
e := Employee{"1","zhangsan",20}
e1 := Employee{Name:"lis",Age:23}
e2 := new(Employee)
e2.Id = "2"
e2.Name = "wangwu"
e2.Age = 20
行为(方法)定义
//该方式在实例对应方法调用时,实例的成员会进行值复制
func (e Employee) String() string {
fmt.Printf("e address is %x ", unsafe.Pointer(&e.Name))
return fmt.Sprintf("ID:%s Name:%s Age:%d", e.Id, e.Name, e.Age)
}
//通常情况下为了避免内存拷贝使用该方式
func (e *Employee) String() string {
fmt.Printf("e address is %x ", unsafe.Pointer(&e.Name))
return fmt.Sprintf("ID:%s Name:%s Age:%d", e.Id, e.Name, e.Age)
}
Go接口
- 接口为非入侵性,实现不依赖于接口定义
- 接口的定义可以包含在接口使用者包内
接口定义
//接口定义
type Programmer interface{
WriteHelloWorld() string
}
//接口实现
type GoProgrammer struct{
}
func (p *GoProgrammer) WriteHelloWorld() string{
return "HelloWrold!"
}
接口变量
var prog string = &GoProgrammer{}
//类型
type GoProgrammer struct{}
//变量
&GoProgrammer{}
自定义类型
//自定义类型
type IntConv func (op int) int
//使用自定义类型
func timeSpent(inner func(op int) int) IntConv{
return func(n int) int {
start := time.Now()
ret := inner(n)
fmt.Println("time spent:",time.Since(start).Seconds())
return ret
}
}
扩展和复用
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Println("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
type Dog struct {
p *Pet
}
func (d *Dog) Speak() {
fmt.Println("wang wang wang")
}
func (d *Dog) SpeakTo(host string) {
d.Speak()
fmt.Println(" ", host)
}
func TestDog(t *testing.T){
d := new(Dog)
d.SpeakTo("dog")
}
多态
//自定义类型
type Code string
//接口
type Programmer interface{
WriteHelloWorld() Code
}
//实现
type GoProgrammer struct{}
type JavaProgrammer struct{}
//重写方法
func (p *GoProgrammer) WriteHelloWorld() Code{
return "GoProgrammer write Go HelloWorld!";
}
func (p *JavaProgrammer) WriteHelloWorld() Code{
return "JavaProgrammer write Java HelloWorld!"
}
func WriteHelloWorld(p Programmer){
fmt.Println(p.WriteHelloWorld())
}
func TestPolymorphism(t *testing.T){
gp := new(GoProgrammer)
WriteHelloWorld(gp)
jp := new(JavaProgrammer)
WriteHelloWorld(jp)
}
空接口与断言
-
空接口可以表示任何类型
-
通过断言来将空接口转换为指定类型
v,ok := p.(int) //ok=true时转换成功
func DoSomething(p interface{}) {
/*
ok := p.(int) : 断言,如果被断言为一个整型
v : 转换后的值
*/
if v, ok := p.(int); ok {
fmt.Println("Integer", v)
return
}
if v, ok := p.(string); ok {
fmt.Println("string", v)
return
}
fmt.Println("unknow type")
}
//使用switch简化
func DoSomethingWithSwitch(p interface{}) {
switch v := p.(type) {
case int:
fmt.Println("Integer", v)
case string:
fmt.Println("string", v)
default:
fmt.Println("unknow type")
}
}
Go的错误机制
- 没有异常机制
- error 类型实现了error接口
- 可以通过 error.New() 来快速创建错误实例
//预先定义错误
var LessThanTwoError = errors.New("n should be in not less than 2")
var LargerThenHundredError = errors.New("n should be not larger than 100")
func print(n int) (int, error) {
if n < 2 {
return 0, LessThanTwoError
}
if n > 100 {
return 0, LargerThenHundredError
}
return n + 1, nil
}
func Test(t *testing.T) {
//错误检查
if v, err := print(-1); err != nil {
if err == LessThanTwoError {
t.Log("error is LessThanTwoError:", err)
} else if err == LargerThenHundredError {
t.Log("error is LargerThenHundredError:", err)
} else {
t.Error(err)
}
} else {
t.Log(v)
}
}
panic
- panic 用于不可恢复的错误
- panic 推出钱会执行 defer 指定的内容
func TestPanic(t *testing.T){
defer func() {
fmt.Println("finally!")
}()
panic(errors.New("errors new!"))
}
recover
- recover() 函数可以捕获 panic 抛出的错误,模拟 try-catch 的功能
- recover()函数只能在 defer 函数中使用
func TestRecover(t *testing.T) {
defer func() {
if err := recover(); err != nil { // 在defer 函数中使用recover()函数捕获错误
fmt.Println("finally!")
//恢复错误...
}
}()
t.Log("start")
panic(errors.New("errors new!"))
}
package
-
基本复用模块单元
以首字母大写来表明可以被包外代码访问
-
代码的 package 可以和所在的目录不一致
-
同一目录中的Go代码的package要保持一致
init 方法
- 在
main
函数被执行前,所有依赖的package
的init
方法都会被执行 - 不同包的
init
函数按照包导入的依赖关系决定执行顺序 - 每个包可以有多个
init
函数 - 包的每个源文件也可以有多个
init
函数
使用外部依赖
比如我们要使用该项目来使用线程安全的map。go get -u https://github.com/easierway/concurrent_map
-
通过
go get
来获取远程依赖go get -u
强制从网络更新远程依赖
go get -u github.com/easierway/concurrent_map
import (
"testing"
//引入远程包,可以取一个cm的别名
cm "github.com/easierway/concurrent_map"
)
func TestConcurrentMap(t *testing.T) {
m := cm.CreateConcurrentMap(32)
m.Set(cm.StrKey("one"), 1)
t.Log(m.Get(cm.StrKey("one")))
t.Log(m.Get(cm.StrKey("two")))
}
Go 并发编程
协程机制
goroutine(协程) 可以看作是一个并发执行的函数,与其他语言的线程(Thread)相比,它具备以下特点:
-
很小的栈空间开销(2KB,可根据需要调整)
-
由 Go 自己管理调度,不依赖于底层系统线程
-
可以很方便地使用 go 关键字启动
-
通过 channel 进行通信(而不是共享内存),更容易控制和组合同步操作
func TestGroutine(t *testing.T) {
for i := 0; i < 10; i++ {
go func(i int) { //使用 go 关键字将函数作为 goroutine 启动
fmt.Println(i)
}(i)
}
time.Sleep(time.Second * 1)
}
共享内存和并发机制
Mutex
sync.Mutex 是一个用于互斥访问共享资源的结构体。它具有两个主要方法:Lock 和 Unlock。
使用 Mutex 可以有效地控制并发访问,避免多个 goroutine 同时访问同一个共享资源,从而导致数据破坏或者不一致的情况发生。
func TestCounterThreadSafe(t *testing.T) {
var mut sync.Mutex
counter := 0
for i := 0; i < 5000; i++ {
go func() {
defer func() { //释放锁
mut.Unlock()
}()
mut.Lock()
counter++
}()
}
time.Sleep(2 * time.Second) //等待所有goroutine执行完毕
t.Logf("counter = %d", counter)
}
WaitGroup
Go 中的 WaitGroup 是一种用于等待多个 goroutine 完成的机制,它可以避免在程序退出前未完成的 goroutine 的情况。
WaitGroup 包含两个方法:Add 和 Done,以及一个属性 Wait。
- Add 方法用于在 WaitGroup 中添加等待的 goroutine 的数量,一般在启动 goroutine 之前调用。
- Done 方法用于在 goroutine 完成时通知 WaitGroup,表示当前等待的 goroutine 数量减一。
- Wait 方法会使当前 goroutine 阻塞,直到所有等待的 goroutine 都执行完毕(即 Done 方法调用的次数等于 Add 方法调用的次数)。
func TestCounterWaitGroup(t *testing.T) {
var mut sync.Mutex
var wg sync.WaitGroup
counter := 0
for i := 0; i < 5000; i++ {
wg.Add(1)
go func() {
defer func() {
mut.Unlock()
}()
mut.Lock()
counter++
wg.Done()
}()
}
wg.Wait()// time.Sleep(2 * time.Second)
t.Logf("counter = %d", counter)
}
CSP 并发机制
CSP(Communicating Sequential Process)并发机制包含三个核心概念:goroutine、channel 和 select。
- Goroutine
Goroutine 是 Go 语言中轻量级线程的概念,在程序中可以创建众多的 goroutine 进行并发处理。与传统操作系统线程相比,goroutine 更加轻量、更高效,它们可以通过 go 关键字来启动,并由 Go 的运行时系统进行管理和调度。一个 goroutine 可以看作一个函数的执行体,每个 goroutine 都有自己的局部变量和栈,可以使用语言内置的关键字 go 以轻量级的方式启动。
- Channel
在 Go 语言中,goroutine 之间通信的主要方式是通过 channel,它是一种类似于队列的数据结构,可以用于两个或多个 goroutine 之间进行数据交换。通常,一个 goroutine 向 channel 中发送数据,另一个 goroutine 从 channel 中接收数据,通过 channel 实现了不同 goroutine 之间的同步和通信。创建一个 channel 只需使用内置函数 make 即可,如:ch := make(chan int)。
- Select
select 语句用于在多个 channel 上进行非阻塞的多路通信操作。select 语句会监听每个 channel 上的数据流动,当 channel 中有数据可用时,就会执行相应的分支。如果多个分支同时准备就绪,select 会随机选择其中一个分支执行,这种特性可以有效地实现复杂的并发模型。在 select 中可以使用 case 关键字处理不同的 channel 操作,select 后面可以跟一个默认分支 default,用于处理还没有准备就绪的 channel 操作。
多路选择和超时 select
多渠道的选择
select{
case ret := <-retCh1:
t.Logf("result %s",ret)
case ret := <-retCh2:
t.Logf("result %s",ret)
default:
t.Error("No one retunted")
}
超时控制
select{
case ret := <-retCh:
t.Logf("result %s",ret)
case <- time.After(time.Second * 1)
t.Error("time out")
}
示例
func service() string {
time.Sleep(time.Millisecond * 50)
return "Done"
}
func AsyncService() chan string {
retCh := make(chan string) //声明一个channel
go func() {
ret := service()
fmt.Println("returned result.")
retCh <- ret //往channel中放数据
fmt.Println("service exited.")
}()
return retCh
}
func TestAsyncService(t *testing.T) {
//多路选择实现超时
select {
case ret := <-AsyncService():
t.Log(ret)
// case <-time.After(time.Millisecond * 100):
case <-time.After(time.Millisecond * 10):
t.Error("time out")
}
}
Chan的广播与关闭
- 向关闭的
channel
发送数据,会导致panic
- v,ok <- ch; ok为bool值,true 表示正常接受,false表示通道关闭
- 所有的channel接受者都会在channel关闭时,立即从阻塞等待中返回且上述ok值为false。这个广播机制常被利用,进行多个订阅者同时发送信号。如:退出信号。
func dataProducer(ch chan int, wg *sync.WaitGroup) {
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch) //关闭channel
wg.Done()
//ch <- 100 向关闭的通道继续发送数据会导致panic
}()
}
func dataReceiver(ch chan int, wg *sync.WaitGroup) {
go func() {
for i := 0; i < 11; i++ {
if data, ok := <-ch; ok { //接收者判断channel是否已经关闭
fmt.Println(data)
} else {
fmt.Println("channel is close!")
break
}
}
wg.Done()
}()
}
func TestCloseChannel(t *testing.T) {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
dataProducer(ch, &wg)
wg.Add(1)
dataReceiver(ch, &wg)
wg.Wait()
}
Chan的取消
在 Go 语言中,可以通过 context 和 close 函数来取消 chan
Close
在 Go 语言中,close 函数是用于关闭一个通道(channel)的。
通道关闭后,通道就不能再进行发送操作。当通道中的所有数据被接收完毕后,接收操作会立即返回一个零值和一个false值,表明通道已经关闭了。如果通道已经关闭,再次进行关闭操作会引起运行时panic。
/*
*
判断任务是否取消
*/
func isCancelled(cancelChan chan struct{}) bool {
select {
case <-cancelChan:
return true
default:
return false
}
}
//关闭chan
func cancel_1(cancelChan chan struct{}) {
cancelChan <- struct{}{}
}
func cancel_2(cancelChan chan struct{}) {
close(cancelChan)
}
/*
*
任务取消
*/
func TestCancel(t *testing.T) {
cancelChan := make(chan struct{}, 0)
for i := 0; i < 5; i++ {
go func(i int, cancelCh chan struct{}) {
for {
if isCancelled(cancelCh) {
break
}
time.Sleep(time.Millisecond * 5)
}
fmt.Println(i, "Canceled")
}(i, cancelChan)
}
cancel_2(cancelChan)
time.Sleep(time.Second * 1)
}
Context
context 是 Go 语言中的一个标准库。它提供了一种在 goroutine 之间传递的上下文信息的机制,可以用于处理超时、取消信号、截止时间等问题,防止程序把一些资源留下来占用,从而保证代码的健壮性和安全性。
- 使用 context 需要创建一个上下文对象,可以通过 context.Background() 或 context.TODO() 来创建
- 子context:context.WithCancel(parentContext)创建
- ctx,cancel := WithCancel(context.Background())
- 当前context被取消时,基于他的context都会被取消
- 接受取消通知:<- ctx.Done()
/*
*
通过context判断channel是否关闭
*/
func isCancelled(context context.Context) bool {
select {
case <-context.Done(): //接收消息通知
return true
default:
return false
}
}
/*
*
任务取消
*/
func TestCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) //创建context
for i := 0; i < 5; i++ {
go func(i int, ctx context.Context) {
for {
if isCancelled(ctx) {
break
}
time.Sleep(time.Millisecond * 5)
}
fmt.Println(i, "Canceled")
}(i, ctx)
}
cancel() //取消context
time.Sleep(time.Second * 1)
}
sync.Once(只执行一次的函数)
sync.Once 是 Go 标准库提供的一种用于实现单例模式的同步机制。
它可以确保某个操作仅在第一次被调用时执行一次,并且后续的调用直接返回第一次执行时的结果,非常适合做单例实现。
type Singleton struct {
}
var singletonInstance *Singleton
var once sync.Once
// 创建一个单例的对象
func GetSingletonObj() *Singleton {
once.Do(func() {
fmt.Println("create obj")
singletonInstance = new(Singleton)
})
return singletonInstance
}
func TestSingleton(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
obj := GetSingletonObj()
fmt.Printf("%d\n", unsafe.Pointer(obj)) //输出单例对象的地址
wg.Done()
}()
}
wg.Wait()
}
使用buffered channel 实现对象池
package obj_pool
import (
"errors"
"time"
)
// Connection 连接信息
type Connection struct {
}
// ConnectionPool 连接对象池
type ConnectionPool struct {
bufChan chan *Connection
}
// NewConnectionPool 创建连接池
// numOfObj channel 大小
func NewConnectionPool(numOfObj int) *ConnectionPool {
connPool := ConnectionPool{}
connPool.bufChan = make(chan *Connection, numOfObj)
for i := 0; i < numOfObj; i++ {
connPool.bufChan <- &Connection{}
}
return &connPool
}
// GetConnection 获取连接
func (p *ConnectionPool) GetConnection(timeout time.Duration) (*Connection, error) {
select {
case ret := <-p.bufChan:
return ret, nil
case <-time.After(timeout):
return nil, errors.New("time out") //超时控制
}
}
// ReleaseConnection 释放连接
func (p *ConnectionPool) ReleaseConnection(conn *Connection) error {
select {
case p.bufChan <- conn:
return nil
default:
return errors.New("overflow")
}
}
测试用例
// 测试对象池
func TestConnectionPool(t *testing.T) {
pool := NewConnectionPool(10)
for i := 0; i < 11; i++ {
if v, err := pool.GetConnection(time.Second * 1); err != nil {
t.Error(err)
} else {
fmt.Printf("%T\n", v)
if err := pool.ReleaseConnection(v); err != nil {
t.Error(err)
}
}
}
t.Log("Done")
}
sync.Pool 对象缓存
sync.Pool是 Go 语言标准库中提供的一种对象池实现方式。
对象池可以用于减少对象创建和垃圾回收的次数,提高程序性能。
sync.Pool 使用可重用的对象来构建一个对象池,以便它们可以在将来的操作中被再次使用,而不必再次分配和初始化。
sync.Pool的特点有:
- 对象池是并发安全的,可以被多个 Goroutine 并发访问。
- 对象池默认底层实现的大小是按需分配的,防止了需要时锁定大小的对象池出现的问题。
- 对象池中的对象在适当的时候可以被垃圾收集器处理掉,而不会一直累计占据内存。
syncPool的生命周期
- GC会清除 sync.Pool 中缓存的对象
- 对象缓存的有效期为下一次GC之前
使用 sync.Pool
- Get() 方法从池中获取一个对象。如果当前池为空,则返回一个新对象。
- Put(x interface{}) 方法将对象放回到池中。如果池已满,则不会加入该对象。
package obj_cache
import (
"fmt"
"sync"
"testing"
)
// TestSyncPool 测试sync.Pool
func TestSyncPool(t *testing.T) {
pool := &sync.Pool{
New: func() interface{} {
fmt.Println("create new Object")
return 100
},
}
v := pool.Get().(int)
fmt.Println("v:", v)
pool.Put(666)
//runtime.GC() //手动GC 清除sync.Pool中缓存的对象
v1 := pool.Get().(int)
fmt.Println("v1:", v1)
v2 := pool.Get().(int)
fmt.Println("v2:", v2)
}
// TestSyncPoolInMultiGroutine 测试多协程情况下sync.Pool
func TestSyncPoolInMultiGroutine(t *testing.T) {
pool := &sync.Pool{
New: func() interface{} {
fmt.Println("create new Object")
return 100
},
}
pool.Put(666)
pool.Put(666)
pool.Put(666)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
fmt.Println(pool.Get().(int))
wg.Done()
}(i)
}
wg.Wait()
}
Go中的反射编程
Go 语言通过反射(reflection)包实现了反射编程。
反射库提供了在运行时检查类型和变量、调用函数等相关的功能,让程序在运行时可以动态地获取类型信息、调用方法等。
- 可以使用 reflect.TypeOf() 方法来获取一个变量的类型信息
- 反射还可以动态地创建和修改对象。可以使用 reflect.New() 方法来创建一个值的指针
- 反射还可以动态地调用对象的方法。可以使用 reflect.ValueOf() 方法获取到一个值的 reflect.Value 类型对象,然后使用 reflect.Value 的 MethodByName() 方法来获取到某个方法,并调用这个方法。通过FieldByName()方法来获取到某个字段的信息。
- 通过kind来判断类型
判断类型--kind
// CheckType 通过kind来判断类型
func CheckType(v interface{}) {
t := reflect.TypeOf(v)
switch t.Kind() {
case reflect.Float32, reflect.Float64:
fmt.Println("Float")
case reflect.Int, reflect.Int32, reflect.Int64:
fmt.Println("Integer")
default:
fmt.Println("Unknown", t)
}
}
func TestBasicType(t *testing.T) {
var f float32 = 1
CheckType(f)
}
TypeOf and ValueOf
type Employee struct {
EmployeeId string
Name string `format:"normal"`
Age int
}
func (e *Employee) UpdateAge(newAge int) {
e.Age = newAge
}
type Customer struct {
CookieId string
Name string
Age int
}
func TestInvokeByName(t *testing.T) {
e := &Employee{"1", "Tom", 30}
//按名字获取成员
t.Log(reflect.ValueOf(*e).FieldByName(`Name`))
t.Logf("Name:value(%[1]v),Type((%[1]T)", reflect.ValueOf(*e).FieldByName(`Name`))
if nameField, ok := reflect.TypeOf(*e).FieldByName(`Name`); !ok {
t.Error("Failed to get Name field.")
} else {
t.Log("Tag:format:", nameField.Tag.Get("format"))
}
//动态调用method
reflect.ValueOf(e).MethodByName("UpdateAge").
Call([]reflect.Value{reflect.ValueOf(23)})
t.Log("UpdateAge:", e)
}
struct Tag
在 Go 语言中,可以使用 Struct Tag 为结构体的各个字段添加元数据信息。
Struct Tag 是通过在字段定义后面添加一个用反引号 `` 包裹的字符串来定义的。
例如:
type Person struct {
Name string `json:"name" xml:"name"`
Age int `json:"age" xml:"age"`
Phone string `json:"phone" xml:"phone"`
}
访问struct tag
// TestStructTag 访问 struct tag
func TestStructTag(t *testing.T) {
p := &Person{"Tom", 23, "6666666"}
if field, ok := reflect.TypeOf(*p).FieldByName("Name"); ok {
t.Log("field tag json:", field.Tag.Get("json"))
t.Log("field tag xml:", field.Tag.Get("xml"))
}
}
DeepEqual
在 Go 语言中,使用 reflect.DeepEqual() 函数可以比较两个任意类型的值是否相等。
func TestDeepEqual(t *testing.T) {
a := map[int]string{1: "one", 2: "two", 3: "three"}
b := map[int]string{1: "one", 2: "two", 3: "three"}
//t.Log(a == b)
t.Log("reflect.DeepEqual(a, b) result :", reflect.DeepEqual(a, b))
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
s3 := []int{2, 3, 1}
t.Log("s1 == s2?", reflect.DeepEqual(s1, s2))
t.Log("s1 == s3?", reflect.DeepEqual(s1, s3))
}
标签:入门,int,fmt,testing,笔记,Golang,func,string,Log
From: https://www.cnblogs.com/keepsilence233/p/17439155.html