2021年1月23日 星期六

Go 語言筆記

  1. 空白標識符、匿名佔位符(blank identifier、anonymous placeholder),底線「_」
    佔位置,不用對不須要的資料多設一個變數
    例1:
    func test() (string, string) {
        return "X", "Y"
    }
    func main() {
        var a, _ = test()
        fmt.Println(a) //X
    }
    

    例2:
    arr := [3]int{10, 20, 30}
    sum := 0
    for _, v := range arr {
        sum += v
    }
    fmt.Println(sum)//60

  2.  不定數量參數,「...」
    例1:陣列元素不定數量
    arr := [...]string{"A", "B", "C"}
    fmt.Println(len(arr))//3

    例2:function參數不定數量
    func test(args ...string) {
        fmt.Println(args) //[A B C]
    }
    func main() {
        test("A", "B", "C")
    }

    例3:slice打散
    sliceA := []string{"A", "B", "C"}
    sliceB := []string{"D", "E"}
    sliceC := append(sliceA, sliceB...)
    fmt.Println(sliceC)//[A B C D E]
  3. [...]string{"A", "B", "C"} 和 []string{"A", "B", "C"} 的差異
    [...]string{"A", "B", "C"} 產生的是陣列 array
    []string{"A", "B", "C"} 產生的是切片 slice  (背後產生一個隱性陣列)
    參考:Confusion with “…” operator in golang
    https://stackoverflow.com/questions/28692410/confusion-with-operator-in-golang
  4. 沒有 while,用 for 實現
    例1:
    i := 0
    for i < 10 {
        i++
    }

    例2:無窮迴圈
    for {
        //break
    }
  5. 沒有 class,用結構(struct)實現。
    沒有繼承,用嵌入組合、介面解決,避免繼承產生的維護難度(多用合成/聚合,少用繼承)。
  6. struct指標語法糖。
    根據struct接受的參數型態,自動進行取址(reference &)或取值。(dereference *)進行操作
    例1:指標自動轉換取值操作
    type aa struct {
        m int
    }
    func main() {
        var ptr = &aa{10}
        fmt.Println(ptr) //&{10}
        ptr.m = 20       //自動等同 (*ptr).m=10
        fmt.Println(ptr) //&{20}
    }

    例2:指標自動轉換取址操作
    type aa struct {
        m int
    }
    func (x *aa) add(v int) { //aa須為指標
        x.m = x.m + v
    }
    func main() {
        var p = aa{10}
        fmt.Println(p) //{10}
        p.add(20)      //自動等同 (&p).add(20)
        fmt.Println(p) //{30}
    }

    例3:指標自動轉換取值操作
    type aa struct {
        m int
    }
    func (x aa) add(v int) { //aa須為值
        x.m = x.m + v
        fmt.Println(x) //{30}
    }
    func main() {
        var p = &aa{10}
        fmt.Println(p) //&{10}
        p.add(20)      //自動等同 (*p).add(20)
        fmt.Println(p) //&{10} 傳值過去,所以原本的變數仍為10
    }
  7. errors
    errors.New()
    fmt.Printf("%#v\n", errors.New("test err"))
    //&errors.errorString{s:"test err"}

    fmt.Errorf()
    fmt.Printf("%#v\n", fmt.Errorf("test err %d", 456))
    //&errors.errorString{s:"test err 456"}
    
    //使用 %w 包裹(嵌套)錯誤
    fmt.Printf("%#v\n", fmt.Errorf("test err2 %w", errors.New("test err")))
    //&fmt.wrapError{msg:"test err2 test err", err:(*errors.errorString)(0xc000032e70)}

    errors.Unwrap() 解開包裹的錯誤,取得裡層的錯誤
    fmt.Printf("%#v\n", errors.Unwrap(fmt.Errorf("test err2 %w", errors.New("test err"))))
    //&errors.errorString{s:"test err"}

    errors.Is(errA, errB) bool
    判斷 errA 是否為 errB 的錯誤
    如果 errA 是包裹的錯誤,會一層一層解開,跟 errB 比對,任一層相符即為true
    var errB = errors.New("test err")
    var errWrap1 = fmt.Errorf("w1 %w", errB)     //包1層
    var errWrap2 = fmt.Errorf("w2 %w", errWrap1) //包2層
    
    fmt.Printf("%#v\n", errB)     //&main.myErr{msg:"test err"}
    fmt.Printf("%#v\n", errWrap1) //&fmt.wrapError{msg:"w1 test err", err:(*errors.errorString)(0xc000032210)}
    fmt.Printf("%#v\n", errWrap2) //&fmt.wrapError{msg:"w2 w1 test err", err:(*fmt.wrapError)(0xc000004500)}
    
    fmt.Printf("%t\n", errors.Is(errB, errWrap1))     //false
    fmt.Printf("%t\n", errors.Is(errWrap1, errWrap2)) //false
    fmt.Printf("%t\n", errors.Is(errWrap1, errB))     //true
    fmt.Printf("%t\n", errors.Is(errWrap2, errB))     //true, errWrap2是包裹的錯誤,會一層一層解開比較
    fmt.Printf("%t\n", errors.Is(errWrap2, errWrap1)) //true

    errors.As(errA, targetB interface{}) bool
    判斷 errA 是否為 targetB 類型的錯誤,如果 errA 是包裹的錯誤,會一層一層解開,跟 targetB 比對
    將第一個比對符合的錯誤,指派到 targetB,並回傳 true
    type myErr struct {
        msg string
    }
    
    func (e *myErr) Error() string {
        return e.msg
    }
    func main() {
        var targetErr *myErr
        errWrap1 := fmt.Errorf("w1 %w", &myErr{msg: "test err"}) //包1層
        var errWrap2 = fmt.Errorf("w2 %w", errWrap1)             //包2層
    
        fmt.Printf("%#v\n", targetErr) //(*main.myErr)(nil)
        fmt.Printf("%#v\n", errWrap1)  //&mt.wrapError{msg:"w1 test err", err:(*main.myErr)(0xc0000321f0)}
        fmt.Printf("%#v\n", errWrap2)  //&fmt.wrapError{msg:"w2 w1 test err", err:(*fmt.wrapError)(0xc0000044a0)}
    
        fmt.Printf("%t\n", errors.As(errWrap1, &targetErr)) //true
        fmt.Printf("%#v\n", targetErr)                      //&main.myErr{msg:"test err"}
    
        fmt.Printf("%t\n", errors.As(errWrap2, &targetErr)) //true
        fmt.Printf("%#v\n", targetErr)                      //&main.myErr{msg:"test err"}
    }
  8. defer 延遲執行,採LIFO後進先出的順序(Last In, First out)
    通常可使用在解鎖互斥鎖,或關閉文件defer f.Close()避免最後忘記關閉原先開啟的文件。
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    //結果
    3
    2
    1
  9. panic() 讓程式失敗中斷,類似其他語言的 throw exception
    可使用在巢狀深層呼叫,發生錯誤時,便於向外傳播
    panic("發生錯誤")
    fmt.Println("hello,world") //不會執行到這裡,程式在前一行已崩潰停止
    //執行結果
    panic: 發生錯誤
    goroutine 1 [running]:
    main.main()
    	d:/Go/test/test.go:29 +0x45
    Process exiting with code: 0
  10. recover(),recover 搭配 defer 可捕捉 panic 產生的錯誤,避免程式中斷崩潰停止
    recover() 回傳值為傳給 panic() 的值,但(所以)在以下情況會是 nil
    A.panic's argument was nil;
    B.the goroutine is not panicking;
    C.recover was not called directly by a deferred function.

    例1:捕捉錯誤,避免系統中斷崩潰停止,但後續程式仍不會執行
    defer func() {
        log.Println("AA")
        err := recover()
        fmt.Println(err)
        log.Println("BB")
    }()
    panic("發生錯誤")
    fmt.Println("hello,world") //雖然程式不會崩潰停止,但依然不會執行到這裡
    //執行結果
    2021/01/18 23:23:29 AA
    2021/01/18 23:23:29 BB
    發生錯誤
    Process exiting with code: 0

    例2:捕捉錯誤,避免系統中斷崩潰停止,後續程式仍可執行
    參考 https://golang.org/ref/spec#Handling_panics
    Handling panics
    //定義一個 protect() 函式,可傳入另一個可能發生panic的函式執行,
    func protect(g func()) {
        defer func() {
            log.Println("done") // Println executes normally even if there is a panic
            if x := recover(); x != nil {
                log.Printf("run time panic: %v", x)
            }
        }()
        log.Println("start")
        g()
    }
    
    func main() {
        protect(func() {
            panic("發生錯誤")
        })
        log.Println("hello,world")
        
        //執行結果
        2021/01/18 23:41:03 start
        2021/01/18 23:41:03 done
        2021/01/18 23:41:03 run time panic: 發生錯誤
        2021/01/18 23:41:03 hello,world
        Process exiting with code: 0
    }
  11. 疑問:log.Printf()、fmt.Println() 穿插寫,每次執行輸出的順序可能不一樣?
    log.Printf("A")
    fmt.Println("B")
    log.Printf("C")
    
    //執行結果1
    2021/01/15 23:38:59 A
    B
    2021/01/15 23:38:59 C
    Process exiting with code: 0
    
    //執行結果2
    2021/01/15 23:38:36 A
    2021/01/15 23:38:36 C
    B
    Process exiting with code: 0
  12. goroutine 併發
    語法:
    go funciotn或method
  13. channel 通道
    多個goroutine併發,之間溝通的管道

    無緩衝通道,Ex:make(chan int)
    有緩衝通道,Ex:make(chan int, 緩衝大小)
  14. 通道 deadlock 死鎖
    • 從沒資料的通道中,接收資料
      ch := make(chan int, 1)
      //ch <- 5
      A := <-ch //
      log.Print(A)
      
    • 空的 select
      select {}
    • 無緩衝通道(或有緩衝通道滿了之後),再發資料給此通道,發送、接收須用goroutine併發分開處理,否則會發生deadlock死鎖

      Ex1:未超出緩衝,不用另一個goroutine接收
      ch := make(chan int, 1) //1個緩衝區
      ch <- 5 //剛好用完緩衝區
      A := <-ch 
      log.Print(A) //A
      Ex2:緩衝滿了之後,發送和接收,須用goroutine併發處理
      ch := make(chan int, 1) //1個緩衝區
      
      ch <- 5 //剛好用完緩衝區
      go func() {
          ch <- 6 //緩衝用完,須用goroutine併發處理
          ch <- 7
      }()
      
      A := <-ch
      log.Print(A) //5
      A = <-ch
      log.Print(A) //6
      A = <-ch
      log.Print(A) //7
    • select 若沒 default, case敘述句跟沒資料的通道取資料、或發送資料給無緩衝區的通道,會出現deadlock死鎖
      ch := make(chan int, 1)
      //ch <- 5
      select {
      case <-ch:
      default:
          log.Print("避免deaklock")
      }
  15. select 通道中的有同時多個 case 條件都符合時,是隨機執行的
    ch := make(chan int, 1)
    //ch <- 5
    select {
    case ch <- 1:
    case ch <- 2:
    case ch <- 3:
    default:
        log.Print("避免deaklock")
    }
    A := <-ch
    log.Print(A) //1、2、3 隨機出現
  16. sync.Mutex 加鎖,避免併發競爭問題
    競爭檢測分析
    go run -race ./main.go
    加入互斥鎖
    var mux sync.Mutex
    mux.Lock()
    defer mux.Unlock()
    唯讀鎖定
    var rwMux sync.RWMutex
    rwMux.Lock()
    defer rwMux.Unlock()
    
    rwMux.RLock() //唯讀鎖定
    defer rwMux.RUnlock()
  17. sync.WaitGroup,等待所有任務完成,可避免併發尚未執行完畢,主程式已結束
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        ....
    }()
    wg.Wait()





參考:
https://www.mdeditor.tw/pl/2dWs/zh-tw
一看就懂系列之Golang的goroutine和通道_Go語言中文網 - MdEditor
https://ithelp.ithome.com.tw/articles/10218923
Channel,  goroutine之間的溝通橋樑 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
https://ithelp.ithome.com.tw/articles/10223617
day26 - 同步 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天




2020年12月28日 星期一

Redis Windows 版

Redis 官網,只提供 Linux 的版本。
但 Microsoft Open Tech 有提供 windows 版本
https://github.com/microsoftarchive/redis/releases
有.msi的安裝檔和免安裝的縮壓檔。

如果用安裝檔,安裝後,會將 Redis 設定成系統服務開機自動啟動,
安裝過程也可選擇是否加入系統 PATH 環境變數、記憶體限制、是否開放防火牆...

如果使用免安裝的縮壓檔,之後想設定成系統服務。
可參考資料夾下的說明文件範例:
安裝成系統服務
redis-server --service-install redis.windows-service.conf --loglevel verbose

移除安裝的系統服務
redis-server --service-uninstall

啟動
redis-server --service-start

停止
redis-server --service-stop

安裝並啟動三個獨立的Redis服務範例:
redis-server --service-install --service-name redisService1 --port 10001
redis-server --service-start --service-name redisService1
redis-server --service-install --service-name redisService2 --port 10002
redis-server --service-start --service-name redisService2
redis-server --service-install --service-name redisService3 --port 10003
redis-server --service-start --service-name redisService3



其他:
https://opensource.microsoft.com
https://github.com/microsoftarchive

2020年12月24日 星期四

Linux 網卡新增多個IP

CentOS,在一張名稱為 enp0s8 的網路卡,設定多個IP



方法一:
使用 ifconfig 指令動態新增IP(網卡重啟後失效)

#新增一個別名 enp0s8:xyz (較常見使用 enp0s8:0、enp0s8:1、...),用來設定新增的IP
$ ifconfig enp0s8:xyz 192.168.56.111/24 up
#關閉別名為 enp0s8:xyz 的設定
$ ifconfig enp0s8:xyz down



方法二:
使用 ip 指令動態新增(網卡重啟後失效)

#不設定別名,直接新增IP
$ ip addr add 192.168.56.111/24 dev enp0s8
#新增一個別名 enp0s8:xyz (較常見使用 enp0s8:0、enp0s8:1、...),用來設定新增的IP
$ ip addr add 192.168.56.111/24 dev enp0s8 label enp0s8:xyz
#刪除指定的IP設定
$ ip addr del 192.168.56.111/24 dev enp0s8 label enp0s8:xyz




方法三:
於原網路卡設定檔,參數加後綴,設定新增的IP

$ vi /etc/sysconfig/network-scripts/ifcfg-enp0s8
.......
###第1個新增的IP
IPADDR1=192.168.56.111
PREFIX1=24
###第2個新增的IP
IPADDR2=192.168.56.112
PREFIX2=24
重啟網路
$ systemctl restart network
或
$ ifdown enp0s8 ; ifup enp0s8;



方法四:
新增網路卡別名設定檔,設定新增的IP

$ vi /etc/sysconfig/network-scripts/ifcfg-enp0s8:xyz1
DEVICE="enp0s8:xyz1"
#/usr/share/doc/initscripts-9.49.53/sysconfig.txt
#ONBOOT="yes" #(not valid for alias devices; use ONPARENT)
ONPARENT="yes"
IPADDR="192.168.56.111"
PREFIX="24" #NETMASK=255.255.255.0
$ vi /etc/sysconfig/network-scripts/ifcfg-enp0s8:xyz2
DEVICE="enp0s8:xyz2"
#ONBOOT="yes" #(not valid for alias devices; use ONPARENT)
ONPARENT="yes"
IPADDR="192.168.56.112"
PREFIX="24" #NETMASK=255.255.255.0
啟用新增的網卡別名設定
$ ifup enp0s8:xyz1
$ ifup enp0s8:xyz2



參考:

AVD Manager 啟動Android模擬器出現 HAXM is not installed on this machine

問題: 
啟動 Android 模擬器,出現錯誤
「emulator: ERROR: x86_64 emulation currently requires hardware acceleration!
Please ensure Intel HAXM is properly installed and usable.
CPU acceleration status: HAXM is not installed on this machine」


解決:

  1. 確定 BIOS 中的「Intel Virtualization Technology」已開啟
  2. 確定 SDK Manager 中已安裝「 Exreas > intel x86 Emulator Accelerator(HAXM installer)」
  3. 到 「android-sdk\extras\intel\Hardware_Accelerated_Execution_Manager」資料夾下,
    執行
    silent_install.bat
    安裝 HAXM

    反安裝 HAXM
    silent_install.bat -u

    檢查是否已安裝 HAXM
    silent_install.bat -v

    檢查 VT/NX 支援
    silent_install.bat -c



參考:
https://www.itread01.com/p/2322.html
Android stdio 解決HAXM安裝不上問題



2020年12月9日 星期三

Node.js npm 安裝套件發生錯誤(operation not supported on socket, symlink)

環境:
將 windows 資料夾共用給 linux 虛擬機,
然後在 linux 上,於 windows 的共用資料夾下安裝 express

$ npm install express --save
npm WARN rollback Rolling back send@0.16.2 failed (this is probably harmless): ENOTEMPTY: directory not empty, rmdir '/home/web/win/aa/nodejs/node_modules/send'
npm WARN rollback Rolling back mime@1.4.1 failed (this is probably harmless): ENOTEMPTY: directory not empty, rmdir '/home/web/win/aa/nodejs/node_modules/mime'
npm WARN rollback Rolling back serve-static@1.13.2 failed (this is probably harmless): ENOTEMPTY: directory not empty, rmdir '/home/web/win/aa/nodejs/node_modules/serve-static'
npm WARN rollback Rolling back express@4.16.3 failed (this is probably harmless): ENOTEMPTY: directory not empty, rmdir '/home/web/win/aa/nodejs/node_modules/express'
npm WARN chat@0.0.1 No repository field.

npm ERR! path ../mime/cli.js
npm ERR! code ENOTSUP
npm ERR! errno -95
npm ERR! syscall symlink
npm ERR! nospc ENOTSUP: operation not supported on socket, symlink '../mime/cli.js' -> '/home/win/nodejs/node_modules/.bin/mime'
npm ERR! nospc There appears to be insufficient space on your system to finish.
npm ERR! nospc Clear up some disk space and try again.

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2018-03-26T09_28_59_107Z-debug.log


解決:
因 windows 共享的資料夾不能建立符號連結(symlinks),所以須加上 --no-bin-links 參數
$ npm install express --no-bin-links --save


參考:




[使用 --no-bin-links  衍生的問題]
如果 在 linux 上掛載 windows 上的專案資料夾,在 linux 安裝時,不得已使用 --no-bin-links,雖可以順利安裝。但因為在 linux 上,node_modules/.bin 資料夾底下是檔案連結,連結到對應的實際套件底下,而掛載的 windows 無法建議檔案連結,所以 node_modules/.bin 底下會沒東西,若專案需要用到 node_modules/.bin 底下的檔案來執行,會無法運作。


參考: