首页 > 其他分享 >Golang入门笔记

Golang入门笔记

时间:2023-05-28 23:33:58浏览次数:49  
标签:入门 int fmt testing 笔记 Golang func string Log

第一个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 语言支持多种运算符,包括算术运算符、赋值运算符、比较运算符、逻辑运算符等

算术运算符

  1. + :加法运算
  2. - :减法运算
  3. * :乘法运算
  4. / :除法运算
  5. % :取模运算(求余数)
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

比较运算符

比较运算符通常用于控制程序的流程或进行条件判断

  1. == :判断两个值是否相等,相等则返回 true,否则返回 false
  2. != :判断两个值是否不相等,不相等则返回 true,否则返回 false
  3. < :判断左侧的值是否小于右侧的值,小于则返回 true,否则返回 false
  4. > :判断左侧的值是否大于右侧的值,大于则返回 true,否则返回 false
  5. <= :判断左侧的值是否小于等于右侧的值,小于等于则返回 true,否则返回 false
  6. >= :判断左侧的值是否大于等于右侧的值,大于等于则返回 true,否则返回 false

逻辑运算符

  1. && :逻辑与运算,左右两侧的表达式都为真,则返回 true,否则返回 false
  2. || :逻辑或运算,左右两侧的表达式有一个为真,则返回 true,否则返回 false
  3. ! :逻辑非运算,对右侧的表达式取反

条件和循环

在 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函数被执行前,所有依赖的packageinit方法都会被执行
  • 不同包的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)相比,它具备以下特点:

  1. 很小的栈空间开销(2KB,可根据需要调整)

  2. 由 Go 自己管理调度,不依赖于底层系统线程

  3. 可以很方便地使用 go 关键字启动

  4. 通过 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的特点有:

  1. 对象池是并发安全的,可以被多个 Goroutine 并发访问。
  2. 对象池默认底层实现的大小是按需分配的,防止了需要时锁定大小的对象池出现的问题。
  3. 对象池中的对象在适当的时候可以被垃圾收集器处理掉,而不会一直累计占据内存。

syncPool的生命周期

  • GC会清除 sync.Pool 中缓存的对象
  • 对象缓存的有效期为下一次GC之前

使用 sync.Pool

  1. Get() 方法从池中获取一个对象。如果当前池为空,则返回一个新对象。
  2. 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

相关文章

  • babylon.js 学习笔记(7)
    前面我们学习了如何画一堆房子(如下图),显然这单调的绿色大地,看上去效果并不好。babylon.js中,可以用图片模拟出地势高低不同的效果,比如下面这张图片:颜色越深的地方,表示地势越低(即:盆地),而颜色越浅的地方,地势越高(即:高山),可以参考下面的代码:constcreateScene=()=>{consts......
  • 有关 PID 控制的入门理论知识
     PID控制算法是自动控制中的一个非常典型且重要的算法,可以明确的说,对于学自动控制的朋友来说,不懂PID控制,那就等于没真正的学过自动控制。PID的应用其实在我们的生活中是很普遍的,它不止浮现于算法介绍的文献中,在生活中也是应用广泛的、比如我们日常使用的恒温热水器、液位稳定系统,......
  • Spring Data JPA 入门
    注解说明@Entity(name="")类注解,用来注解该类是一个实体类并用来和数据库中的表建立关联关系。其中name表示该表的名称@Table(name="")类注解,跟@Entity(name="")作用一致@Id属性注解,该注解表明该属性字段是一个主键,该属性必须具备,不可缺少@GeneratedValue(strategy=......
  • 《人月神话》阅读笔记七
    1、保持进度透明可见我们的团队在做项目的过程中,总是少不了实时汇报自己负责的部分进度,我们有时可能会担心,如果我们向上级或者负责人汇报了我们的完成进度,就有可能会因为某些问题扰乱我们的进度和计划。其实,如果我们隐瞒不报,就有可能助长我们的侥幸心理,从而在更大程度上影响我......
  • 并发相关笔记一
    并发和并行并行:指两个或多个时间在同一时刻发生(同时发生);并发:指两个或多个事件在一个时间段内发生。在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单CPU系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感......
  • 「学习笔记」(扩展)中国剩余定理
    有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?该问题出自《孙子算经》,具体问题的解答口诀由明朝数学家程大位在《算法统宗》中给出:三人同行七十希,五树梅花廿一支,七子团圆正半月,除百零五便得知。\(2\times70+3\times21+2\times15=233=2\times......
  • 《HTML入门笔记2》
    HTML常用标签分别有:a标签、img标签、table标签、form标签、input标签等。a标签(特别常用)a标签即超级链接,又叫超链接。一个网站通常由多个页面构成,进入网站时首先看到的就是其首页,如果想从首页跳转至其他页面,就需要在首页相应的位置添加超级链接。a标签其基本语法格式如......
  • 用redis项目练习笔记,跟着黑马敲,并有自己的理解在里面
    点评中,优惠卷牵扯到的秒杀问题。超卖现象如果多线程同时执行会因为高并发,先查询再插入之间会有空档时间,发生超卖问题。可以使用悲观锁或者乐观锁解决,出于对性能的考虑,用到了乐观锁。乐观锁的实现,用到了数据库where语句多加一个条件。每次判断跟上次相同,(这样会造成大量的失......
  • 软件工程日报——《人间》读书笔记
    总结以下《人件》这本书中涉及到的几个概念和建议1、帕金森定律帕金森定律讲述了如下的定律:如果一个很平庸的人作了管理,那么摆在它面前的只有三条路:退位给有能力的人。使用比自己更优秀的属下。运用比自己还平庸的手下。第一条路和第二条路一般是个有欲望的人,都不会采取,......
  • 【ABAQUS文档笔记】实体单元
    来自ABAQUSDOCUMENT/GETTINGSTARTEDWITHABAQUS/CAE/USINGCONTINUUMELEMENTS单元公式和积分fullintegration“完全积分”是指当单元具有规则形状时,对单元刚度矩阵中的多项式项进行精确积分所需的高斯点数。对于六面体和四边形元素,“规则形状”意味着边缘是直的,并以直......