Golang学习 5

本文深入探讨Golang编程,包括反射机制,详细解释了reflect包的用法,如获取类型和值信息。接着,文章介绍了并发概念,讲解了goroutine、channel以及并发模型GMP。此外,还讨论了网络编程的基础,如TCP和UDP,并简述了单元测试和测试覆盖率的计算方法。
摘要由CSDN通过智能技术生成

18、反射

变量 :
内在机制在于 ,可以分为 类型信息 与 值信息 ,前者存储的是预先定义好的元信息 ,后者存储的是动态变化内容

反射 :
指的是在程序运行期间 对程序本身进行访问与修改的能力

程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们

本质 : 反射就是在运行时动态的获取一个变量的类型信息和值信息

reflect 包 :
任何接口值 ,都可以理解为 reflect.type 与 reflect.value 组成

typeof
获取任意值的类型 reflect.type ,其中类型又可以分为 name 与kind两类,kind更加基础,指底层的类型

func reflectType(x interface{}) {
	t := reflect.TypeOf(x)
	fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}

valueof
返回的是 reflect.value 类型,并且包含了原始值的信息 ,可以相互转换

方法说明
Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool将值以 bool 类型返回
Bytes() []bytes将值以字节数组 []bytes 类型返回
String() string将值以字符串类型返回
func reflectValue(x interface{}) {
	v := reflect.ValueOf(x)
	k := v.Kind()
	switch k {
	case reflect.Int64:
		// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
		fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
	case reflect.Float32:
		// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
		fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
	case reflect.Float64:
		// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
		fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
	}
}

通过反射来设置值
由于函数传参是值传递的 ,因此如果直接使用setint()会引发panic,有专门的elem()来进行取地址

func reflectSetValue2(x interface{}) {
	v := reflect.ValueOf(x)
	// 反射中使用 Elem()方法获取指针对应的值
	if v.Elem().Kind() == reflect.Int64 {
		v.Elem().SetInt(200)
	}
}

isnil() :判断指针是否为空
isvalid() : 判断返回值是否有效 reflect.ValueOf(b).FieldByName(“abc”).IsValid() 判断字段是否存在

结构体反射
结构体的方法一定要大写

方法说明
Field(i int) StructField根据索引,返回索引对应的结构体字段的信息。
NumField() int返回结构体成员字段数量。
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息。
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。
FieldByNameFunc(match func(string) bool) (StructField,bool)根据传入的匹配函数匹配需要的字段。
NumMethod() int返回该类型的方法集中方法的数目
Method(int) Method返回该类型方法集中的第i个方法
MethodByName(string)(Method, bool)根据方法名返回该类型方法集中的方法
// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
		var args = []reflect.Value{}
		v.Method(i).Call(args)

19、并发

并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天)。
并行:同一时刻执行多个任务(你和你朋友都在用微信和女朋友聊天)。

goroutine类似于线程,属于用户态的线程,可以根据需要创建很多goroutine
goroutine由go的runtime(运行时)调度完成 ,线程则由操作系统调度完成

多个goroutine之间 通过channel 进行通信
CSP(Communicating Sequential Process)并发模式 :通信顺序进程

main函数当中 就是一个主goroutine ,当主goroutine结束之后 ,当中的别的goroutine直接结束
启动多个goroutine时 ,可以使用time.sleep ,也可以使用sync.waitgroup

var wg sync.WaitGroup

func hello(i int) {
	defer wg.Done() // goroutine结束就登记-1
	fmt.Println("Hello Goroutine!", i)
}
func main() {

	for i := 0; i < 10; i++ {
		wg.Add(1) // 启动一个goroutine就登记+1
		go hello(i)
	}
	wg.Wait() // 等待所有登记的goroutine都结束
}

OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB

GMP:
区别于操作系统调度os线程,GMP是运行时runtime调度的

  • G很好理解,就是个goroutine的,里面除了存放本goroutine信息外 还有与所在P的绑定等信息。
  • M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行的;
  • P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。

P与M一般也是一一对应的。他们关系是: P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M

P的个数是通过runtime.GOMAXPROCS设定(最大256 )

共享内容实现通信容易发生数据竞态问题,只能通过互斥锁来保护数据,性能低下
Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信

channel
var 变量 chan 元素类型 , 引用类型

var ch chan int
ch = make(chan int)
...

ch1 := make(chan bool,10)
cn2 := make(chan []int )

三个操作 : 发送 , 接受 ,关闭
ch <- 10
x := <- ch 或者 <-ch
close(ch)

无缓冲的channel: 无缓冲的channel在执行发送操作时 会阻塞
ch := make(chan int) 只能在有channel接受值的时候才能发送值

func recv(c chan int) {
	ret := <-c
	fmt.Println("接收成功", ret)
}
func main() {
	ch := make(chan int)
	go recv(ch) // 启用goroutine从通道接收值
	ch <- 10
	fmt.Println("发送成功")
}

有缓冲的channel : make 指定channel大小
ch := make(chan int,10)
len(ch) cap(ch)

一些注意
channel关闭之后再发送会引发panic ,关闭之后再关闭也会引发panic
channel关闭之后取值会将值取完,再取就会取对应类型的零值

如何取值:

for i := range ch2 { // 通道关闭后会退出for range循环
		fmt.Println(i)
	}
.....
for {
    i, ok := <- ch
    if !ok{
        break
    }
    fmt.Println(i)
}

还有单向通道:
func f1(out <-chan int, in chan<- int ) {}
<-chan 表示只能从通道当中取值 ,只读
chan<- 表示只能往通道当中写入值 ,只写

goroutine池 (worker pool):
worker pool 当中可以指定开启的goroutine数量,防止goroutine泄露或者是暴涨 ,小例子:

func worker(id int, jobs <-chan int, results chan<- int) {
	for j := range jobs {
		fmt.Printf("worker:%d start job:%d\n", id, j)
		time.Sleep(time.Second)
		fmt.Printf("worker:%d end job:%d\n", id, j)
		results <- j * 2
	}
}


func main() {
	jobs := make(chan int, 100)
	results := make(chan int, 100)
	// 开启3个goroutine
	for w := 1; w <= 3; w++ {
		go worker(w, jobs, results)
	}
	// 5个任务
	for j := 1; j <= 5; j++ {
		jobs <- j
	}
	close(jobs)
	// 输出结果
	for a := 1; a <= 5; a++ {
		<-results
	}
}

select 多路复用:
场景 : 同时从多个channel当中接收数据 ,多个满足的case当中随机选择一个执行,否则阻塞等待

func main() {
	ch := make(chan int, 1)
	for i := 0; i < 10; i++ {
		select {
		case x := <-ch:
			fmt.Println(x)
		case ch <- i:
		}
	}
}

并发安全与锁
当多个goroutine同时操作一个临界区时,会发送数据竞态问题
使用互斥锁可以保证同一个时间 只有一个goroutine进入临界区,其他的goroutine等待当前互斥锁的释放

var x int64
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
	for i := 0; i < 5000; i++ {
		lock.Lock() // 加锁
		x = x + 1
		lock.Unlock() // 解锁
	}
	wg.Done()
}
func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(x)
}

读写互斥锁:
读多写少的场景下,应该使用sync.RWMutex,可以分为读锁与写锁,当一个goroutine获得读锁之后,其余goroutine如果获取读锁直接获取,获取写锁则等待,当一个goroutine获取写锁之后,其余goroutine无论获取读锁还是写锁都要等待

sync.waitgroup
sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成

sync.once 只执行一次的操作 func (o *Once) Do(f func()) {}

var loadIconsOnce sync.Once
....
loadIconsOnce.Do(func1())

基于sync.once的单例 :

package singleton

import (
    "sync"
)

type singleton struct {}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}

sync.map 线程安全的map
store ,load 等函数

20、网络编程

socket : IP + PORT

TCP

server :

net.listen 建立socket
listen.accept 建立连接
启动一个goroutine 进行处理

// tcp/server/main.go

// TCP server端

// 处理函数
func process(conn net.Conn) {
	defer conn.Close() // 关闭连接
	for {
		reader := bufio.NewReader(conn)
		var buf [128]byte
		n, err := reader.Read(buf[:]) // 读取数据
		if err != nil {
			fmt.Println("read from client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("收到client端发来的数据:", recvStr)
		conn.Write([]byte(recvStr)) // 发送数据
	}
}

func main() {
	listen, err := net.Listen("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	for {
		conn, err := listen.Accept() // 建立连接
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		go process(conn) // 启动一个goroutine处理连接
	}
}

client:

net.dial 建立socket
conn.read conn.write

// tcp/client/main.go

// 客户端
func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("err :", err)
		return
	}
	defer conn.Close() // 关闭连接
	inputReader := bufio.NewReader(os.Stdin)
	for {
		input, _ := inputReader.ReadString('\n') // 读取用户输入
		inputInfo := strings.Trim(input, "\r\n")
		if strings.ToUpper(inputInfo) == "Q" { // 如果输入q就退出
			return
		}
		_, err = conn.Write([]byte(inputInfo)) // 发送数据
		if err != nil {
			return
		}
		buf := [512]byte{}
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Println("recv failed, err:", err)
			return
		}
		fmt.Println(string(buf[:n]))
	}

粘包问题 :
“粘包”可发生在发送端也可发生在接收端:

  1. 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
  2. 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。

UDP :

server:

func main() {
	listen, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		var data [1024]byte
		n, addr, err := listen.ReadFromUDP(data[:]) // 接收数据
		if err != nil {
			fmt.Println("read udp failed, err:", err)
			continue
		}
		fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
		_, err = listen.WriteToUDP(data[:n], addr) // 发送数据
		if err != nil {
			fmt.Println("write to udp failed, err:", err)
			continue
		}
	}
}

client:

func main() {
	socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("连接服务端失败,err:", err)
		return
	}
	defer socket.Close()
	sendData := []byte("Hello server")
	_, err = socket.Write(sendData) // 发送数据
	if err != nil {
		fmt.Println("发送数据失败,err:", err)
		return
	}
	data := make([]byte, 4096)
	n, remoteAddr, err := socket.ReadFromUDP(data) // 接收数据
	if err != nil {
		fmt.Println("接收数据失败,err:", err)
		return
	}
	fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}

21、单元测试

TDD(Test Driven Development)

_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中

类型格式作用
测试函数函数名前缀为Test测试程序的一些逻辑行为是否正确
基准函数函数名前缀为Benchmark测试函数的性能
示例函数函数名前缀为Example为文档提供示例文档

测试函数 :

func TestName(t *testing.T){
    // ...
}

可以使用 reflect.deepequal 进行对比

直接执行 go test 就行
go test -cover -coverprofile=c.out 测试覆盖率

基准测试:

func BenchmarkSplit(b *testing.B) {   
//b.N不是固定的数字   
for i:=0;i<b.N ;i++  
{      Split("三天三夜","三")  
}
}

示例函数:

func ExampleSplit() {
	fmt.Println(split.Split("a:b:c", ":"))
	fmt.Println(split.Split("沙河有沙又有河", "沙"))
	// Output:
	// [a b c]
	// [ 河有 又有河]
}

包含 //Output

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值