美文网首页
golang 指针类型引起的神奇 bug

golang 指针类型引起的神奇 bug

作者: kenuo | 来源:发表于2019-05-26 02:43 被阅读0次

下面是使用的结构体接口抽象定义,其实就是将结构体存进一个 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)」创作共享协议,转载或使用请署名并注明出处。

相关文章

  • golang 指针类型引起的神奇 bug

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

  • Golang基础(三)——复杂类型

    Golang基础(三)——复杂类型 @([07] golang)[Go总结] [TOC] 指针 定义 指针变量可以...

  • golang的指针类型,unsafe.Pointer类型和uin

    一,区别 1,指针类型 golang支持指针类型,指针类型的变量存的是一个内存地址,这个地址指向的内存空间存的才是...

  • go问题整理

    初学golang,记录下日常遇到的问题 golang struct 字段是否被赋值 使用指针类型解决,代码示例 g...

  • (十四)golang unsafe.Pointer

    golang 的指针Go语言是个强类型语言。也就是说Go对类型要求严格,不同类型不能进行赋值操作。指针也是具有明确...

  • 初级问题

    1、Golang make和new的区别 makenew返回值返回对应的引用类型分配零值填充的T类型,并返回指针 ...

  • 切片指针类型的应用——golang

    今天在力扣上做二叉树前序遍历的题目时,遇到一个困惑的问题,先来看看我用递归实现的代码: 看着这么简洁优雅的代码,自...

  • golang实战笔记

    golang实战笔记 一、集合类型 1.值指针和引用类型a、go语言变量持有相应的值,列外:通道、函数、方法、映射...

  • 深拷贝和常见一些坑

    golang 完全是按值传递,所以正常的赋值都是值拷贝,当然如果类型里面嵌套的有指针,也是指针值的拷贝,此时就会出...

  • bug定级标准

    bug类型: 功能类、界面类、性能类、稳定性类、兼容类 bug定级: 阻塞bug: 1、常规操作引起的系统崩溃、死...

网友评论

      本文标题:golang 指针类型引起的神奇 bug

      本文链接:https://www.haomeiwen.com/subject/fegttctx.html