2020年5月21日 星期四

golang channel buffered vs unbuffered

buffered vs unbuffered channel 有些網路上文章以 同步與異步來解釋,雖然感覺上有同步與異步,但其並非同步與異步。非常單純就只是有沒有多buffer空間

先看一個案例
func main() {
    c := make(chan bool, 1)
    go func() {
        fmt.Println("go func()", <-c)
        fmt.Println("go func()", <-c)
    }()

    c <- true
    time.Sleep(1 * time.Second)

    c <- false
    c <- true

    fmt.Println("main()")

}
執行結果
go func() true
... (sleep 1 sec) ...
go func() false
main()

就寫入channel而言,只要還有buffer,就在寫入後立即往下執行,反之,會等待有多餘的空間。因此有同步的感覺,事實上,程式仍是在不同的協程中以非同步進行。

就讀出channel而言,有資料就會讀出後立即往下執行,反之,會等待有資料才讀出往下執行。

所以別再以同步或非同步(異步)來理解channel。而是以 阻塞(blocked)來理解才是正確的唷!

另外加以下二個思考題,請大家自行測試,

channel宣告改為以下buffer size = 2 ,執行結果如何?
    c := make(chan bool, 2)
channel宣告的buffer size = 0,大家覺得成立嗎,還是會有error呢?
    c := make(chan bool, 0)

希望以上的解說,能導正對channel的認知

2020年3月10日 星期二

程式效能之路(1) 變數宣告的時機影響記憶體分配的次數

情境: 每次loop都需要新的變數來進行運算,且為確保每次運算獨立,所以在每進入loop就重新宣告(配置)變數。


func () test() {
    for i := 0; i < 1000000000; i++ {
        var (
            v11, v12, v13, v21, v22, v23 float64
        )
        //...
        //...
        //...
    }
}
這樣看似平常,但在每次loop都會進行記憶體分配與釋放,都是很花費cpu time。


所以應調整成如下:
func () test() {
    var (
        v11, v12, v13, v21, v22, v23 float64

        initVar = func() {
            v11, v12, v13, v21, v22, v23 = 0, 0, 0, 0, 0, 0
        }
    )

    for i := 0; i < 1000000000; i++ {
        initVar()
        //...
        //...
        //...
    }
}
只有進loop時做宣告(配置)變數,而在每次loop開始將變數初始化

golang inner func() 讓code變優雅變精巧


原始 for loop裡在進入時會進行許多的資料判別是否可以往下運行。程式看起來非常的冗長
func (a *ArbTriangular) stg() {
    var (
        askDetph   map[string]db.Order10Depth
        lastTicker map[string]db.LastTicker
    )
    // ...
    // ...
    // ...
    for _, st := range *a.sTriangular {
        if st.isTrading {
            continue
        }
        if _, b := askDetph[st.a]; !b {
            continue
        }
        if _, b := askDetph[st.b]; !b {
            continue
        }
        if _, b := askDetph[st.c]; !b {
            continue
        }
        if _, b := lastTicker[st.a]; !b {
            continue
        }
        if _, b := lastTicker[st.b]; !b {
            continue
        }
        if _, b := lastTicker[st.c]; !b {
            continue
        }
        // ...
        // ...
        // ...
    }
}

進行一次的重構,for loop 看起來好多了,但是冗長的if判別看起來還是不夠優雅
func (a *ArbTriangular) stg() {
    var (
        askDetph map[string]db.Order10Depth
        bidDetph map[string]db.Order10Depth
    )
    checkData := func(st SymboleTriangular) (b bool) {
        if st.isTrading {
            return
        }
        if _, b = askDetph[st.a]; !b {
            return
        }
        if _, b = askDetph[st.b]; !b {
            return
        }
        if _, b = askDetph[st.c]; !b {
            return
        }
        if _, b = bidDetph[st.a]; !b {
            return
        }
        if _, b = bidDetph[st.b]; !b {
            return
        }
        if _, b = bidDetph[st.c]; !b {
            return
        }
        b = true
        return
    }
    //...
    //...
    //...
    for _, st := range *a.sTriangular {
        if !checkData(st) {
            continue
        }
        //...
        //...
        //...
    }
}

進行第二次的重構,這樣的成果優雅又精簡巧
func (a *ArbTriangular) stg() {
    var (
        askDetph map[string]db.Order10Depth
        bidDetph map[string]db.Order10Depth

        existAsk = func(s string) (b bool) {
            _, b = askDetph[s]
            return
        }
        existBid = func(s string) (b bool) {
            _, b = bidDetph[s]
            return
        }
        checkData = func(st SymboleTriangular) bool {
            if st.isTrading ||
                !existAsk(st.a) || !existAsk(st.b) || !existAsk(st.c) ||
                !existBid(st.a) || !existBid(st.b) || !existBid(st.c) {
                return false
            }
            return true
        }
    )
    //...
    //...
    //...
    for _, st := range *a.sTriangular {
        if !checkData(st) {
            continue
        }
        //...
        //...
        //...
    }
}

2020年3月2日 星期一

golang mongodb bson.M 如何解析多維陣列(multi-dimension slice : [][]interfae{}),或未知欄位數的資料?

mangodb中一維的資料可以採用 []interface{}來轉換取出,如下例:
    type User struct {
        Id_     bson.ObjectId `bson:"_id"`
        Name    string        `bson:"name"`
        Age     int           `bson:"age"`
        Friends []interface{} `bson:"friends"`
    }



但是若mondodb中的Friends記錄的是多維的資料,如: [][][]interface{}
確無法單純把定義改為如下,雖然程式執行不會有錯誤,但是資料內容確只有一個空的slice。
    type User struct {
        Id_     bson.ObjectId     `bson:"_id"`
        Name    string            `bson:"name"`
        Age     int               `bson:"age"`
        Friends [][][]interface{} `bson:"friends"`
    }

或,單一interface{}去接資料,也只是得到一個nil。
    type User struct {
        Id_     bson.ObjectId `bson:"_id"`
        Name    string        `bson:"name"`
        Age     int           `bson:"age"`
        Friends interface{}   `bson:"friends"`
    }

解決方式: 不定義結構,先用 interface{}將所有欄位資料取回再後續處理
func GetData() {
    var i interface{}
    var err error
    if i, err = mgoDao.GetMongoData(); err == nil && i != nil {
        if reflect.TypeOf(i.(bson.M)["friends"]) == reflect.TypeOf([]interface{}{}) {
            for _, arr1 := range i.(bson.M)["friends"].([]interface{}) {
                if reflect.TypeOf(arr1) == reflect.TypeOf([]interface{}{}) {
                    for _, arr2 := range arr1.([]interface{}) {
                        if reflect.TypeOf(arr2) == reflect.TypeOf([]interface{}{}) {
                            if len(arr2.([]interface{})) > 2 {
                                friendName := fmt.Sprintf("%v", arr2.([]interface{})[0])
                                friendTel := fmt.Sprintf("%v", arr2.([]interface{})[1])
                                friendDesc := fmt.Sprintf("%v", arr2.([]interface{})[2])
                                if debugLog {
                                    stdLog.Println(tools.GoID(), friendName, friendTel, friendDesc)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return
}




如何對sql 查詢select不重覆的資料

不論concurrency併發 或 parallelism平行,  coroutine協程、multi-thread線程 或multi-process行程, 甚至corss-platform跨平台(可視為多行程),的向同一資料表以相同條件query select 不重複資料。也就是單一應用情境取出不重複資料。

案例:將大量資料取出並加以運算後傳到其它系統。

解決方式,有二個關鍵。
一是不論多協程,多線程或多行程,都必需加上lock,以確保不會有同時query的狀況。
二是,查詢出來後加上標記,以確保不會被其它查詢取出。

lock:
1.若只是multi-thread的程式,可以採用程式用mutex lock再加上一些技巧來完成。
2.但是若是multi-process 甚至corss-platform的多程式,就要使用database的lock機制。此例,建議採用user lock(mysql: select get_lock), app resource lock(mssql: sp_getapplock)。不建議採用row lock或table lock,這會導致其它應用也無法取得此資料。

加上已讀取標記
1.multi-thread 或 coroutine (goroutine):
程式只會在一個平台執行單一行程(process),而行程中可以多執行緒併發執行。而此標記可以做在程式裡,以減少db io,提供執行效率。
2.若是multi-process行程 或corss-platform跨平台,就只能在每次查詢出來後,對每一筆update加上標記。若考慮執行效率,可以採用redis記錄讀取標記,以減少db io。

mysql user lock

    defer func() {
        if tx != nil {
            if getLock == 1 && releaseLock == 0 {
                releaseLock, _ = tx.ReleaseSessionLock(SESSION_LOCK_KEY)
            }
            if err != nil {
                if errRollback := tx.Rollback(); errRollback != nil {
                    err = errors.New(err.Error() + "\n" + errRollback.Error())
                }
            }
        }
    }()
    if tx, err = dao.Begin(); err == nil {
        if getLock, err = tx.GetSessionLock(SESSION_LOCK_KEY, mysqlLockTimeout); err == nil && getLock == 1 {
            myJobs.IncWorkingJob()
            defer myJobs.DecWorkingJob()
            if datas, err = tx.GetData(procLimit, realmLocate); err == nil && kwds != nil {
                if releaseLock, err = tx.ReleaseSessionLock(SESSION_LOCK_KEY); err == nil {
                    rIDs, nIDs := DataProcess(datas)
                    if rIDs != "" { //完成
                        removeIdx(rIDs)
                    }
                    if err == nil && nIDs != "" { //未完成
                        removeIdx(nIDs)
                    }
                }
            }
        }
        if err == nil {
            if getLock == 1 && releaseLock == 0 {
                releaseLock, _ = tx.ReleaseSessionLock(SESSION_LOCK_KEY)
            }
            err = tx.Commit()
        }
    }


func (t *MyTx) GetData(l, r int) (datas []Data, err error) {
    sqlStr := "SELECT idx, f1, f2 FROM " + dataTb + " WHERE dt is null"
    if lenIdx() > 0 {
        sqlStr += " AND idx not in " + whereIdx()
    }
    sqlStr += " ORDER BY RAND() LIMIT ?"
    if rows, e := t.Query(sqlStr, l); e != nil {
        errLog.Println(tools.GoID(), dataTb, e)
        err = e
    } else {
        for rows.Next() {
            data := Data{}
            if err = rows.Scan(tools.Strut2Slice(&data)...); err == nil {
                kwds = append(datas, data)
                addIdx(strconv.FormatUint(data.IDx, 10))
            } else {
                errLog.Println(tools.GoID(), dataTb, err)
            }
        }
    }
    return
}
func whereIdx() (s string) {
    selectedIdxLock.Lock()
    defer selectedIdxLock.Unlock()
    s = ""
    for k := range selectedIdx {
        s += k + ","
    }
    s = strings.Trim(s, ",")
    if s != "" {
        s = "(" + s + ")"
    }
    return
}
func lenIdx() int {
    selectedIdxLock.Lock()
    defer selectedIdxLock.Unlock()
    return len(selectedIdx)
}
func addIdx(s string) {
    selectedIdxLock.Lock()
    defer selectedIdxLock.Unlock()
    selectedIdx[s] = true
}
func resetIdx() {
    selectedIdxLock.Lock()
    defer selectedIdxLock.Unlock()
    selectedIdx = make(map[string]bool)
}
func removeIdx(s string) {
    selectedIdxLock.Lock()
    defer selectedIdxLock.Unlock()
    if s != "" {
        ids := strings.Split(s, ",")
        for _, id := range ids {
            delete(selectedIdx, id)
        }
    }
}

2020年2月11日 星期二

CSV exprot 匯出不亂碼 (加BOM控制字元)

Web應用常有匯出CSV檔,但在number 或 excel 開啟時,會有亂碼,尤其是ms excel。
就算是body內容已轉UTF8,開啟仍然是亂碼。這要如何解決呢?

在body內容最前面輸出BOM碼,即可。

UTF8BOM = "\xEF\xBB\xBF"

在原本body (不是header) 最前面加上輸出 UTF8BOM





ex : PHP echo UTF8BOM . "{$column_name}\n{$column_data}";