一、合理地使用并发特性
1、了解goroutine的生命期时再创建goroutine
代码如下:
package main
import (
"fmt"
"runtime"
)
//一段耗时的计算函数
func consumer(ch chan int) {
//无限获取数据的循环
for {
//从通道获取数据
data := <-ch
//打印数据
fmt.Println(data)
}
}
func main() {
//创建一个传递数据用的通道
ch := make(chan int)
for {
//空变量,什么也不做
var dummy string
//获取输入,模拟进程持续运行
fmt.Scan(&dummy)
//启动并发执行consumer()函数
go consumer(ch)
//输出现在的goroutine数量
fmt.Println("goroutines:", runtime.NumGoroutine())
}
}
运行结果如下:死循环
代码如下:
package main
import (
"fmt"
"runtime"
)
//一段耗时的计算函数
func consumer(ch chan int) {
//无限获取数据的循环
for {
//从通道获取数据
data := <-ch
if data == 0 {
break
}
//打印数据
fmt.Println(data)
}
}
func main() {
//创建一个传递数据用的通道
ch := make(chan int)
for {
//空变量,什么也不做
var dummy string
//获取输入,模拟进程持续运行
fmt.Scan(&dummy)
if dummy == "quit" {
for i := 0; i < runtime.NumGoroutine()-1; i++ {
ch <- 0
}
continue
}
//启动并发执行consumer()函数
go consumer(ch)
//输出现在的goroutine数量
fmt.Println("goroutines:", runtime.NumGoroutine())
}
}
运行结果如下:死循环
2、避免在不必要的地方使用通道
代码如下:
package main
import (
"fmt"
"net"
"time"
)
//套接字接收过程
func socketRecv(conn net.Conn, exitChan chan string) {
//创建一个接收的缓冲
buff := make([]byte, 1024)
//不停地接收数据
for {
//从套接字中读取数据
_, err := conn.Read(buff)
//需要结束接收,退出循环
if err != nil {
break
}
}
//函数已经结束,发送通知
exitChan <- "recv exit"
}
func main() {
//连接一个地址
conn, err := net.Dial("tcp", "www.163.com:80")
//发生错误时打印错误退出
if err != nil {
fmt.Println(err)
return
}
//创建退出通道
exit := make(chan string)
//并发执行套接字接收
go socketRecv(conn, exit)
//在接收时,等待1秒
time.Sleep(time.Second)
//主动关闭套接字
conn.Close()
//等待goroutine退出完毕
fmt.Println(<-exit)
}
运行结果如下:
recv exit
代码如下:
package main
import (
"fmt"
"net"
"sync"
"time"
)
//套接字接收过程
func socketRecv(conn net.Conn, wg *sync.WaitGroup) {
//创建一个接收的缓冲
buff := make([]byte, 1024)
//不停地接收数据
for {
//从套接字中读取数据
_, err := conn.Read(buff)
//需要结束接收,退出循环
if err != nil {
break
}
}
//函数已经结束,发送通知
wg.Done()
}
func main() {
//连接一个地址
conn, err := net.Dial("tcp", "www.163.com:80")
//发生错误时打印错误退出
if err != nil {
fmt.Println(err)
return
}
//退出通道
var wg sync.WaitGroup
//添加一个任务
wg.Add(1)
//并发执行接收套接字
go socketRecv(conn, &wg)
//在接收时,等待1秒
time.Sleep(time.Second)
//主动关闭套接字
conn.Close()
//等待goroutine退出完毕
wg.Wait()
fmt.Println("recv done")
}
运行结果如下:
recv done
二、反射:性能和灵活性的双刃剑
创建一个文件夹reflecttest,创建一个文件reflect_test.go。
代码如下:
package main
import (
"reflect"
"testing"
)
// 声明一个结构体,拥有1个字段
type data struct {
Hp int
}
func BenchmarkNativeAssign(b *testing.B) {
// 实例化结构体
v := data{Hp: 2}
// 停止基准测试的计时器
b.StopTimer()
// 重置基准测试计时器数据
b.ResetTimer()
// 重新启动基准测试计时器
b.StartTimer()
// 根据基准测试数据进行循环测试
for i := 0; i < b.N; i++ {
// 结构体成员赋值测试
v.Hp = 3
}
}
func BenchmarkReflectAssign(b *testing.B) {
v := data{Hp: 2}
// 取出结构体指针的反射值对象,并取其元素
vv := reflect.ValueOf(&v).Elem()
// 根据名字取结构体成员
f := vv.FieldByName("Hp")
b.StopTimer()
b.ResetTimer()
b.StartTimer()
for i := 0; i < b.N; i++ {
// 反射测试设置成员值性能
f.SetInt(3)
}
}
func BenchmarkReflectFindFieldAndAssign(b *testing.B) {
v := data{Hp: 2}
vv := reflect.ValueOf(&v).Elem()
b.StopTimer()
b.ResetTimer()
b.StartTimer()
for i := 0; i < b.N; i++ {
// 测试结构体成员的查找和设置成员的性能
vv.FieldByName("Hp").SetInt(3)
}
}
func foo(v int) {
}
func BenchmarkNativeCall(b *testing.B) {
for i := 0; i < b.N; i++ {
// 原生函数调用
foo(0)
}
}
func BenchmarkReflectCall(b *testing.B) {
// 取函数的反射值对象
v := reflect.ValueOf(foo)
b.StopTimer()
b.ResetTimer()
b.StartTimer()
for i := 0; i < b.N; i++ {
// 反射调用函数
v.Call([]reflect.Value{reflect.ValueOf(2)})
}
}
运行结果如下:
PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\reflecttest> go test -v -bench=.
testing: warning: no tests to run
PASS
ok _/C_/Users/a-xiaobodou/OneDrive_-_Microsoft/Projects/Go/reflecttest 0.326s [no tests to run]
PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\reflecttest>
三、接口的nil判断
四、map的多键索引——多个数值条件可以同时查询
五、优雅地处理TCP粘包
本文探讨了Go语言中合理使用并发特性,如理解goroutine生命周期和避免不必要的通道使用,以及在实际案例中如何优雅处理。此外,还分析了反射的双刃剑特性,通过基准测试展示了反射在性能上的影响。最后,提到了接口的nil判断、多键索引查询以及TCP粘包的处理策略,提供了相关代码示例。
327

被折叠的 条评论
为什么被折叠?



