下面是使用的結構體介面抽象定義,其實就是將結構體存進一個 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
也使用到了鎖機制。
完整 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 解析的時候就不會發生資料衝突的問題了。
資料衝突
上圖就是造成資料衝突的原因.
需要檢視資料衝突命令 -race
go run -race main.go
致謝
感謝你花時間閱讀,如果覺得作者寫的可以,可以將本站分享給更多的人。寫的不好別噴哈,小弟水平有限~~ ???
原文連結:https://www.zhanggaoyuan.com/article/18
原文標題:[golang指標型別引起的神奇bug]
本站使用「 署名-非商業性使用 4.0 國際 (CC BY-NC 4.0)」創作共享協議,轉載或使用請署名並註明出處。