一种go协程间内存零拷贝的消息通信机制

该博客探讨了在Go语言中如何高效地使用channel处理字符串,避免深拷贝和内存碎片。通过分析string类型的channel,建议使用string指针类型的channel以减少内存分配和回收。同时提出了一种解决方案,利用固定大小的底层数组和bytes.Buffer,减少内存操作,提高性能。
摘要由CSDN通过智能技术生成

1. 直接上代码

func (s *ProjectPipeLineService) ApplyAppDoSSHCmd(host, cmd string) (string, error) {
	user := "root"
	password := "12345678"
	port := "22"
	config := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{ssh.Password(password)},
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
	}

	results := make(chan string, 1)   // 字符串通道
	timeout := time.After(10 * time.Second)

    // 执行命令的协程
	go func(host string, port string) {
		results <- sshUtils.ExecuteCmd(cmd, host, port, config)   // 把执行结果通过通道发送给result
	}(host, port)

	select {
	case res := <-results:  // 从通道接收结果,保存在res
		if res != ""{
			return res, nil
		}
	case <-timeout:
		errMsg := fmt.Sprintf("Timed out as ssh host %s and exec cmd %s! \n", host, cmd)
		glog.Error(errMsg)
		return "", errors.New(errMsg)
	}

	return "", errors.New(fmt.Sprintf("host %s, cmd %s fail", host, cmd))
}

上面的测试代码,开启协程来执行shell命令,协层间通过channel进行消息通信

 

2. string类型的channel   

results := make(chan string, 1)  // 这个1,说明这个channel可容纳1个string类型的对象

    此处,容易引起歧义,为啥是make(chan string, 1),而不是 make(chan string, 1024), 字符串长度,难道不该足够容器结果字串?

    咱们来看看string类型的定义,在go的源码中src/runtime/string.go,string的定义如下:

type stringStruct struct {
    str unsafe.Pointer
    len int
}

  可以看出,在给string类型的对象赋值时,是通过str这个指针指向某个存储字符串的内存地址。

  因此,string类型的channel,它发送/接收的是 stringStruct这个结构体类型的变量本身,而不是它指向的字符串

 

3. string类型的channel,发送/接收都会有一次string对象赋值时的深拷贝,不适合

   因此,咱们用string指针类型的channel

     results := make(chan *string, 1)    // string指针类型的channel

       

4. 是否有种方法避免频繁的分配/回收字符串S呢?

    string指针类型,还存在一个问题就是,每次发送/接收消息,不同的消息实体,string指向的这块只读区域,会频繁的分配和释放,时间久了,会积累很多内存碎片,下面的程序提供了一种方法。

import (
    "fmt"
    "bytes"
)

type Buffer struct {
	buf      []byte 
	off      int    
	lastRead readOp 
}

var addr [512]byte

func send(c chan bytes.Buffer, n string, v int) {
   
   sbuf := bytes.NewBuffer(addr[0:0])   // 通过切片关联,而其底层数组是固定的
   
   _, err := sbuf.WriteString(fmt.Sprintf("%s,%d", n, v))
   
   if err == nil {
       c <- *buf
   }

}

    定义一足够容纳长度的底层数组,通过切片操作,构建一个bytes.Buffer对象,后续对这个Buffer进行读写操作就可以了。

    1)底层数组足够容纳信息,addr[0:0]切片操作,切片首索引就是底层数组的首索引,通过切片的读写,也就是对底层数组的读写

    2)发送/接收bytes.Buffer对象,避免了发送/接收整个消息,也避免了频繁分配/回收整个消息体字符串

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值