Go 由于不支持泛型而臭名昭著,但最近,泛型已接近成为现实。Go 团队实施了一个看起来比较稳定的设计草案,并且正以源到源翻译器原型的形式获得关注。本文讲述的是泛型的最新设计,以及如何自己尝试泛型。
成都创新互联公司-专业网站定制、快速模板网站建设、高性价比南县网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式南县网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖南县地区。费用合理售后完善,十载实体公司更值得信赖。
例子
FIFO Stack
假设你要创建一个先进先出堆栈。没有泛型,你可能会这样实现:
type Stack []interface{}func (s Stack) Peek() interface{} {
return s[len(s)-1]
}
func (s *Stack) Pop() {
*s = (*s)[:
len(*s)-1]
}
func (s *Stack) Push(value interface{}) {
*s =
append(*s, value)
}
但是,这里存在一个问题:每当你 Peek 项时,都必须使用类型断言将其从 interface{} 转换为你需要的类型。如果你的堆栈是 *MyObject 的堆栈,则意味着很多 s.Peek().(*MyObject)这样的代码。这不仅让人眼花缭乱,而且还可能引发错误。比如忘记 * 怎么办?或者如果您输入错误的类型怎么办?s.Push(MyObject{})` 可以顺利编译,而且你可能不会发现到自己的错误,直到它影响到你的整个服务为止。
通常,使用 interface{} 是相对危险的。使用更多受限制的类型总是更安全,因为可以在编译时而不是运行时发现问题。
泛型通过允许类型具有类型参数来解决此问题:
type Stack(type T) []Tfunc (s Stack(T)) Peek() T {
return s[len(s)-1]
}
func (s *Stack(T)) Pop() {
*s = (*s)[:
len(*s)-1]
}
func (s *Stack(T)) Push(value T) {
*s =
append(*s, value)
}
这会向 Stack 添加一个类型参数,从而完全不需要 interface{}。现在,当你使用 Peek() 时,返回的值已经是原始类型,并且没有机会返回错误的值类型。这种方式更安全,更容易使用。(译注:就是看起来更丑陋,^-^)
此外,泛型代码通常更易于编译器优化,从而获得更好的性能(以二进制大小为代价)。如果我们对上面的非泛型代码和泛型代码进行基准测试,我们可以看到区别:
type MyObject struct {
X
int
}
var sink MyObjectfunc BenchmarkGo1(b *testing.B) {
for i := 0; i b.N; i++ {
var s Stack
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink = s.Peek().(MyObject)
}
}
func BenchmarkGo2(b *testing.B) {
for i := 0; i b.N; i++ {
var s Stack(MyObject)
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink = s.Peek()
}
}
结果:
BenchmarkGo1BenchmarkGo1-16 12837528 87.0 ns/op 48 B/op 2 allocs/opBenchmarkGo2BenchmarkGo2-16 28406479 41.9 ns/op 24 B/op 2 allocs/op
在这种情况下,我们分配更少的内存,同时泛型的速度是非泛型的两倍。
合约(Contracts)
上面的堆栈示例适用于任何类型。但是,在许多情况下,你需要编写仅适用于具有某些特征的类型的代码。例如,你可能希望堆栈要求类型实现 String() 函数
Go语言的分析取决于多方面的因素,如代码量、理解深度、复杂性等。如果是一般的小规模项目,比如几百行代码,那么通常需要几个小时来分析。但对于大规模项目,比如几万行以上,则需要更多的时间来分析,可能会有几天或更长的时间。同时,如果开发者对代码深入理解的程度越高,分析的时间也会更长。
package p2p
import (
"context"
"errors"
"time"
net "gx/ipfs/QmPjvxTpVH8qJyQDnxnsxF9kv9jezKD1kozz1hs3fCGsNh/go-libp2p-net"
manet "gx/ipfs/QmV6FjemM1K8oXjrvuq3wuVWWoU2TLDPmNnKrxHzY3v6Ai/go-multiaddr-net"
ma "gx/ipfs/QmYmsdtJ3HsodkePE3eU3TsCaP2YvPZJ4LoXnNkDE5Tpt7/go-multiaddr"
pro "gx/ipfs/QmZNkThpqfVXs9GNbexPrfBbXSLNYeKrE7jwFM2oqHbyqN/go-libp2p-protocol"
pstore "gx/ipfs/QmZR2XWVVBCtbgBWnQhWk2xcQfaR3W8faQPriAiaaj7rsr/go-libp2p-peerstore"
p2phost "gx/ipfs/Qmb8T6YBBsjYsVGfrihQLfCJveczZnneSBqBKkYEBWDjge/go-libp2p-host"
peer "gx/ipfs/QmdVrMn1LhB4ybb8hMVaMLXnA8XRSewMnK6YqXKXoTcRvN/go-libp2p-peer"
)
//P2P结构保存当前正在运行的流/监听器的信息
// P2P structure holds information on currently running streams/listeners
type P2P struct {
//监听器
Listeners ListenerRegistry
//数据流
Streams StreamRegistry
//节点ID
identity peer.ID
//节点地址
peerHost p2phost.Host
//一个线程安全的对等节点存储
peerstore pstore.Peerstore
}
//创建一个新的p2p结构
// NewP2P creates new P2P struct
//这个新的p2p结构不包含p2p结构中的监听器和数据流
func NewP2P(identity peer.ID, peerHost p2phost.Host, peerstore pstore.Peerstore) *P2P {
return P2P{
identity: identity,
peerHost: peerHost,
peerstore: peerstore,
}
}
//新建一个数据流 工具方法 构建一个有节点id,内容和协议的流
func (p2p P2P) newStreamTo(ctx2 context.Context, p peer.ID, protocol string) (net.Stream, error) {
//30s 后会自动timeout
ctx, cancel := context.WithTimeout(ctx2, time.Second 30) //TODO: configurable?
defer cancel()
err := p2p.peerHost.Connect(ctx, pstore.PeerInfo{ID: p})
if err != nil {
return nil, err
}
return p2p.peerHost.NewStream(ctx2, p, pro.ID(protocol))
}
//对话为远程监听器创建新的P2P流
//创建一个新的p2p流实现对对话的监听
// Dial creates new P2P stream to a remote listener
//Multiaddr是一种跨协议、跨平台的表示格式的互联网地址。它强调明确性和自我描述。
//对内接收
func (p2p P2P) Dial(ctx context.Context, addr ma.Multiaddr, peer peer.ID, proto string, bindAddr ma.Multiaddr) ( ListenerInfo, error) {
//获取一些节点信息 network, host, nil
lnet, _, err := manet.DialArgs(bindAddr)
if err != nil {
return nil, err
}
//监听信息
listenerInfo := ListenerInfo{
//节点身份
Identity: p2p.identity,
////应用程序协议标识符。
Protocol: proto,
}
//调用newStreamTo 通过ctx(内容) peer(节点id) proto(协议标识符) 参数获取一个新的数据流
remote, err := p2p.newStreamTo(ctx, peer, proto)
if err != nil {
return nil, err
}
//network协议标识
switch lnet {
//network为"tcp", "tcp4", "tcp6"
case "tcp", "tcp4", "tcp6":
//从监听器获取新的信息 nla.Listener, nil
listener, err := manet.Listen(bindAddr)
if err != nil {
if err2 := remote.Reset(); err2 != nil {
return nil, err2
}
return nil, err
}
//将获取的新信息保存到listenerInfo
listenerInfo.Address = listener.Multiaddr()
listenerInfo.Closer = listener
listenerInfo.Running = true
//开启接受
go p2p.doAccept(listenerInfo, remote, listener)
default:
return nil, errors.New("unsupported protocol: " + lnet)
}
return listenerInfo, nil
}
//
func (p2p *P2P) doAccept(listenerInfo *ListenerInfo, remote net.Stream, listener manet.Listener) {
//关闭侦听器并删除流处理程序
defer listener.Close()
//Returns a Multiaddr friendly Conn
//一个有好的 Multiaddr 连接
local, err := listener.Accept()
if err != nil {
return
}
stream := StreamInfo{
//连接协议
Protocol: listenerInfo.Protocol,
//定位节点
LocalPeer: listenerInfo.Identity,
//定位节点地址
LocalAddr: listenerInfo.Address,
//远程节点
RemotePeer: remote.Conn().RemotePeer(),
//远程节点地址
RemoteAddr: remote.Conn().RemoteMultiaddr(),
//定位
Local: local,
//远程
Remote: remote,
//注册码
Registry: p2p.Streams,
}
//注册连接信息
p2p.Streams.Register(stream)
//开启节点广播
stream.startStreaming()
}
//侦听器将流处理程序包装到侦听器中
// Listener wraps stream handler into a listener
type Listener interface {
Accept() (net.Stream, error)
Close() error
}
//P2PListener保存关于侦听器的信息
// P2PListener holds information on a listener
type P2PListener struct {
peerHost p2phost.Host
conCh chan net.Stream
proto pro.ID
ctx context.Context
cancel func()
}
//等待侦听器的连接
// Accept waits for a connection from the listener
func (il *P2PListener) Accept() (net.Stream, error) {
select {
case c := -il.conCh:
return c, nil
case -il.ctx.Done():
return nil, il.ctx.Err()
}
}
//关闭侦听器并删除流处理程序
// Close closes the listener and removes stream handler
func (il *P2PListener) Close() error {
il.cancel()
il.peerHost.RemoveStreamHandler(il.proto)
return nil
}
// Listen创建新的P2PListener
// Listen creates new P2PListener
func (p2p P2P) registerStreamHandler(ctx2 context.Context, protocol string) ( P2PListener, error) {
ctx, cancel := context.WithCancel(ctx2)
list := P2PListener{
peerHost: p2p.peerHost,
proto: pro.ID(protocol),
conCh: make(chan net.Stream),
ctx: ctx,
cancel: cancel,
}
p2p.peerHost.SetStreamHandler(list.proto, func(s net.Stream) {
select {
case list.conCh - s:
case -ctx.Done():
s.Reset()
}
})
return list, nil
}
// NewListener创建新的p2p侦听器
// NewListener creates new p2p listener
//对外广播
func (p2p P2P) NewListener(ctx context.Context, proto string, addr ma.Multiaddr) ( ListenerInfo, error) {
//调用registerStreamHandler 构造一个新的listener
listener, err := p2p.registerStreamHandler(ctx, proto)
if err != nil {
return nil, err
}
//构造新的listenerInfo
listenerInfo := ListenerInfo{
Identity: p2p.identity,
Protocol: proto,
Address: addr,
Closer: listener,
Running: true,
Registry: p2p.Listeners,
}
go p2p.acceptStreams(listenerInfo, listener)
//注册连接信息
p2p.Listeners.Register(listenerInfo)
return listenerInfo, nil
}
//接受流
func (p2p *P2P) acceptStreams(listenerInfo *ListenerInfo, listener Listener) {
for listenerInfo.Running {
//一个有好的 远程 连接
remote, err := listener.Accept()
if err != nil {
listener.Close()
break
}
}
//取消注册表中的p2p侦听器
p2p.Listeners.Deregister(listenerInfo.Protocol)
}
// CheckProtoExists检查是否注册了协议处理程序
// mux处理程序
// CheckProtoExists checks whether a protocol handler is registered to
// mux handler
func (p2p *P2P) CheckProtoExists(proto string) bool {
protos := p2p.peerHost.Mux().Protocols()
for _, p := range protos {
if p != proto {
continue
}
return true
}
return false
}
继续进入下一个初始化
n点虐 Service, err = nebnet.NewNebService(n)
if err != nil {
logging.CLog().WithFields(logrus.Fields{
"err": err,
}).Fatal("Failed to setup net service.")
}
netservice有两个成员
type NebServicestruct {
node *Node
dispatcher *Dispatcher
}
跳出stup()函数
先进入start()函数看一看
if err := n点虐 Service.Start(); err != nil {
logging.CLog().WithFields(logrus.Fields{
"err": err,
}).Fatal("Failed to start net service.")
}
进入netservice.start()
func (ns *NebService) Start() error {
logging.CLog().Info("Starting NebService...")
// start dispatcher.
ns.dispatcher.Start()
// start node.
if err := ns.node.Start(); err != nil {
ns.dispatcher.Stop()
logging.CLog().WithFields(logrus.Fields{
"err": err,
}).Error("Failed to start NebService.")
return err
}
logging.CLog().Info("Started NebService.")
return nil
}
可以看到第一个start()的函数是dispatcher.start()
进入dispatch.start()
func (dp *Dispatcher) Start() {
logging.CLog().Info("Starting NebService Dispatcher...")
go dp.loop()
}
然后就出现一个新的线程、goruntime
go dp.loop()
进入该线程,看它干了些什么
timerChan := time.NewTicker(time.Second).C
for {
select {
case -timerChan:
metricsDispatcherCached.Update(int64(len(dp.receivedMessageCh)))
case -dp.quitCh:
logging.CLog().Info("Stoped NebService Dispatcher.")
return
case msg := -dp.receivedMessageCh:
msgType := msg.MessageType()
v, _ := dp.subscribersMap.Load(msgType)
if v == nil {
continue
}
m, _ := v.(*sync.Map)
m.Range(func(key, valueinterface{}) bool {
select {
case key.(*Subscriber).msgChan - msg:
default:
logging.VLog().WithFields(logrus.Fields{
"msgType": msgType,
}).Warn("timeout to dispatch message.")
}
return true
})
}
}
一个有点长的循环
metricsDispatcherCached.Update(int64(len(dp.receivedMessageCh)))一秒钟刷新一次缓冲区
case msg := -dp.receivedMessageCh:
msgType := msg.MessageType()如果能取出dp.receivedMessageCh
msgType := msg.MessageType()首先判断取出的信息类型
v, _ := dp.subscribersMap.Load(msgType)
if v == nil {
continue
}
根据类型取出相应的map
如果取不出,那么使用continue结束这个case
m, _ := v.(*sync.Map)
断言
m.Range(func(key, valueinterface{}) bool {
select {
case key.(*Subscriber).msgChan - msg:
default:
logging.VLog().WithFields(logrus.Fields{
"msgType": msgType,
}).Warn("timeout to dispa+tch message.")
}
return true
})
将msg推入其他管道里面去。其他goruntime会循环等待该