分享
  1. 首页
  2. 文章

golang 指针类型引起的神奇 bug

kenuo · · 1718 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

下面是使用的结构体接口抽象定义,其实就是将结构体存进一个 map里。由于是读写都比较频繁,我加了读写锁。

// add progress listener.
func (upload *UploaderGateway) AddProgress(key string, v ProgressListener) {
 upload.mutex.Lock()
 defer upload.mutex.Unlock()
 upload.ProgressMap[key] = v
}
//get progress listener.
func (upload *UploaderGateway) GetProgress(key string) (v ProgressListener, err error) {
 upload.mutex.RLock()
 defer upload.mutex.RUnlock()
 progressListener, ok := upload.ProgressMap[key]
 if !ok {
 return nil, errors.New("Get ProgressListener Not Found")
 }
 listener := progressListener.GetFormat()
 return &listener, nil
}
//delete progress listener.
func (upload *UploaderGateway) DeleteProgress(key string) {
 upload.mutex.Lock()
 defer upload.mutex.Unlock()
 delete(upload.ProgressMap, key)
}

结构体定义

// 定义进度条监听器。
type OssProgressListener struct {
 FileSha1 string `json:"file_sha1"` //file sha1
 FileSize int64 `json:"file_size"` //file size
 ConsumedBytes int64 `json:"consumed_bytes"` //consumed bytes
 mutex *sync.RWMutex
}
// set consumed bytes.
func (listener *OssProgressListener) SetConsumedBytes(value int64) ProgressListener {
 listener.mutex.Lock()
 defer listener.mutex.Unlock()
 listener.ConsumedBytes = value
 return listener
}
// 定义进度变更事件处理函数。
func (listener *OssProgressListener) ProgressChanged(event *oss.ProgressEvent) {
 listener.SetConsumedBytes(event.ConsumedBytes)
 //pretty.Printf("event: %# v\n", event)
 //pretty.Printf("listener: %# v\n", listener)
 switch event.EventType {
 case oss.TransferStartedEvent:
 fmt.Printf("传输已启动,已用字节数: %d, 总计字节: %d.\n",
 event.ConsumedBytes, listener.FileSize)
 case oss.TransferDataEvent:
 //if event.ConsumedBytes == 0 || listener.FileSize == 0 {
 // fmt.Printf("传输数据,消耗字节: %d, 总计字节: %d, %d%%.\n",
 // event.ConsumedBytes, listener.FileSize, event.ConsumedBytes)
 //} else {
 // fmt.Printf("传输数据,消耗字节: %d, 总计字节: %d, %d%%.\n",
 // event.ConsumedBytes, listener.FileSize, event.ConsumedBytes*100/listener.FileSize)
 //}
 case oss.TransferCompletedEvent:
 fmt.Printf("\n传输已完成,已用字节数: %d, 总计字节: %d.\n",
 event.ConsumedBytes, listener.FileSize)
 case oss.TransferFailedEvent:
 fmt.Printf("\n传输失败,已用字节数: %d, 总计字节: %d.\n",
 event.ConsumedBytes, listener.FileSize)
 default:
 }
}

上面的 ProgressChanged 函数是个回调函数,上传进度会实时调用,然后去更新ConsumedBytes 的值.

那么问题来了。当我调用 GetProgress 的时候,就会把将 OssProgressListener 结构体信息返回出去,由于是api形式,框架底层会将结构体解析成json然后返回给浏览器。 那么解析json的时候底层还是会去读取这个结构体的值信息。造成读写并发的问题。

解决思路

首先实现抽象一个结构返回这个结构体信息加锁,因为我们写数据 ConsumedBytes 也使用到了锁机制。

image.png

完整 Progress定义

/**
* Author: JeffreyBool
* Date: 2019年5月25日
* Time: 19:13
* Software: GoLand
*/
package oss
import (
 "github.com/aliyun/aliyun-oss-go-sdk/oss"
 "fmt"
 "sync"
 "encoding/json"
)
type ProgressListener interface {
 oss.ProgressListener
 SetFileSha1(string) ProgressListener
 SetFileSize(int64) ProgressListener
 SetConsumedBytes(int64) ProgressListener
 GetFormat() OssProgressListener
}
// 定义进度条监听器。
type OssProgressListener struct {
 FileSha1 string `json:"file_sha1"` //file sha1
 FileSize int64 `json:"file_size"` //file size
 ConsumedBytes int64 `json:"consumed_bytes"` //consumed bytes
 mutex *sync.RWMutex
}
//初始化进度条监听器
func NewOssProgressListener() ProgressListener {
 return &OssProgressListener{mutex: new(sync.RWMutex)}
}
// set file sha1.
func (listener *OssProgressListener) SetFileSha1(value string) ProgressListener {
 listener.mutex.Lock()
 defer listener.mutex.Unlock()
 listener.FileSha1 = value
 return listener
}
// set file size.
func (listener *OssProgressListener) SetFileSize(value int64) ProgressListener {
 listener.mutex.Lock()
 defer listener.mutex.Unlock()
 listener.FileSize = value
 return listener
}
// set consumed bytes.
func (listener *OssProgressListener) SetConsumedBytes(value int64) ProgressListener {
 listener.mutex.Lock()
 defer listener.mutex.Unlock()
 listener.ConsumedBytes = value
 return listener
}
//获取数据
//只能为了防止 json 序列化再次读取这个值和写冲突,就使用值拷贝的方式。
func (listener *OssProgressListener) GetFormat() OssProgressListener {
 listener.mutex.RLock()
 defer listener.mutex.RUnlock()
 //bytes, _ := listener.Marshal()
 return *listener
}
//json 序列化加锁..防止数据冲突
func (listener *OssProgressListener) Marshal() ([]byte, error) {
 listener.mutex.RLock()
 defer listener.mutex.RUnlock()
 return json.Marshal(listener)
}
// 定义进度变更事件处理函数。
func (listener *OssProgressListener) ProgressChanged(event *oss.ProgressEvent) {
 listener.SetConsumedBytes(event.ConsumedBytes)
 //pretty.Printf("event: %# v\n", event)
 //pretty.Printf("listener: %# v\n", listener)
 switch event.EventType {
 case oss.TransferStartedEvent:
 fmt.Printf("传输已启动,已用字节数: %d, 总计字节: %d.\n",
 event.ConsumedBytes, listener.FileSize)
 case oss.TransferDataEvent:
 //if event.ConsumedBytes == 0 || listener.FileSize == 0 {
 // fmt.Printf("传输数据,消耗字节: %d, 总计字节: %d, %d%%.\n",
 // event.ConsumedBytes, listener.FileSize, event.ConsumedBytes)
 //} else {
 // fmt.Printf("传输数据,消耗字节: %d, 总计字节: %d, %d%%.\n",
 // event.ConsumedBytes, listener.FileSize, event.ConsumedBytes*100/listener.FileSize)
 //}
 case oss.TransferCompletedEvent:
 fmt.Printf("\n传输已完成,已用字节数: %d, 总计字节: %d.\n",
 event.ConsumedBytes, listener.FileSize)
 case oss.TransferFailedEvent:
 fmt.Printf("\n传输失败,已用字节数: %d, 总计字节: %d.\n",
 event.ConsumedBytes, listener.FileSize)
 default:
 }
}

上面的 GetFormat 就是我们对外暴露的信息。 注意 读取的时候 加锁.然后我们需要返回这个结构体的值传递类型,一定不要返回指针类型。默认值传递类型会将数据拷贝一份返回出去,这样外面拿到的数据就不是同一个变量地址的数据啦。这样做 json 解析的时候就不会发生数据冲突的问题了。

image.png

数据冲突

image.png

上图就是造成数据冲突的原因.

需要查看数据冲突命令 -race

go run -race main.go

致谢

感谢你花时间阅读,如果觉得作者写的可以,可以将本站分享给更多的人。写的不好别喷哈,小弟水平有限~~ ????????????

原文链接:https://www.zhanggaoyuan.com/article/18

原文标题:[golang指针类型引起的神奇bug]

本站使用「 署名-非商业性使用 4.0 国际 (CC BY-NC 4.0)」创作共享协议,转载或使用请署名并注明出处。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:kenuo

查看原文:golang 指针类型引起的神奇 bug

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1718 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏