golang 指針類型引發的神奇 bug

下面是使用的結構體接口抽象定義,其實就是將結構體存進一個 map裏。因爲是讀寫都比較頻繁,我加了讀寫鎖。git

// 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 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
}

// 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 的值.github

那麼問題來了。當我調用 GetProgress 的時候,就會把將 ProgressListener (ProgressListener 是接口:指針類型,指針傳遞) 的實現 *OssProgressListener 結構體信息返回出去,因爲是api形式,框架底層會將結構體解析成json而後返回給瀏覽器。 那麼解析json的時候底層仍是會去讀取這個結構體的值信息。形成讀寫併發的問題。golang

解決思路

首先實現抽象一個結構返回這個結構體信息加鎖,由於咱們寫數據 ConsumedBytes 也使用到了鎖機制。json

image.png

完整 Progress定義

//get progress listener.
func (upload *UploaderGateway) GetProgress(key string) (v *OssProgressListener, 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
}

複製代碼

這裏 progressListener 接受到的是值傳遞,因此外面修改不會對 ProgressListener 形成影響api

/** * 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函數

致謝

感謝你花時間閱讀,若是以爲做者寫的能夠,能夠將本站分享給更多的人。寫的很差別噴哈,小弟水平有限~~ 🤣🤣🤣ui

原文連接:www.zhanggaoyuan.com/article/18

原文標題:[golang指針類型引發的神奇bug]

本站使用「 署名-非商業性使用 4.0 國際 (CC BY-NC 4.0)」創做共享協議,轉載或使用請署名並註明出處。

相關文章
相關標籤/搜索