golang 指针类型引起的神奇 bug

栏目: C · 发布时间: 6年前

内容简介:下面是使用的结构体接口抽象定义,其实就是将结构体存进一个上面的那么问题来了。当我调用

下面是使用的结构体接口抽象定义,其实就是将结构体存进一个 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 也使用到了锁机制。

golang 指针类型引起的神奇 bug

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 解析的时候就不会发生数据冲突的问题了。

golang 指针类型引起的神奇 bug

image.png

数据冲突

golang 指针类型引起的神奇 bug

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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Essential ActionScript 3.0

Essential ActionScript 3.0

Colin Moock / Adobe Dev Library / June 22, 2007 / $34.64

ActionScript 3.0 is a huge upgrade to Flash's programming language. The enhancements to ActionScript's performance, feature set, ease of use, cleanliness, and sophistication are considerable. Essentia......一起来看看 《Essential ActionScript 3.0》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码