pretty code
2025年5月8日 星期四
使用全域變數真的要小心,尤其在 Golang
2025年4月22日 星期二
又回來 Golang 的世界了
2023年11月20日 星期一
交接專案待釐清事項
2023年9月6日 星期三
Coding is magic
2023年8月31日 星期四
The GUI framework of Go - Wails
2023年1月11日 星期三
JSON array with array value in Go
2022年1月1日 星期六
手裡拿著一把槌子,看什麼都覺得是釘子
2021年9月10日 星期五
Named Capture Group in Golang
2021年8月26日 星期四
Golang os/exec command
2021年6月25日 星期五
A pointer variable in the loop statement of Golang
package main
import (
"fmt"
)
type Device struct {
ID string
}
type Arg struct {
Devices []*Device
}
func main() {
arg := Arg {
Devices : []*Device{
&Device {
ID : "1",
},
&Device {
ID : "2",
},
},
}
d3 := &Device {
ID : "3",
}
for i, d := range arg.Devices {
fmt.Printf("idx(%d) d=%p arg.Devices[%d]=%p d3=%p\n", i, d, i, arg.Devices[i], d3)
//d = d3
arg.Devices[i] = d3
fmt.Printf("idx(%d) d=%p arg.Devices[%d]=%p d3=%p\n", i, d, i, arg.Devices[i], d3)
}
for i, d := range arg.Devices {
fmt.Println(i, d.ID)
}
}
2021年3月22日 星期一
init function in Go package
2021年3月4日 星期四
Golang data race with fmt package
Goruntine 是 Go 語言裡面一個很重要的功能,它是一個輕量化的執行緒,由 Go runtime 負責去排程執行,據官方說法,同時開啟好幾千個也沒問題。
使用 Golang 開發專案時或多或少都會使用到 Goruntine,我們也很習慣在 Goruntine 裡面直接使用 fmt package,但 fmt 並不保證 concurrency safe。
最近我在專案中使用了 GIN 和 logrotate,發現在某些情況下會導致 data race,底下便是我精簡過的程式碼。
package main
import (
"bufio"
"errors"
"path/filepath"
"fmt"
"log"
"os"
"sync"
"time"
)
type Options struct {
Directory string
FileNameFunc func() string
}
type Writer struct {
logger *log.Logger
opts Options
f *os.File
bw *bufio.Writer
bytesWritten int64
queue chan []byte
pending sync.WaitGroup
closing chan struct{}
done chan struct{}
}
func (w *Writer) Write(p []byte) (n int, err error) {
//p := make([]byte, len(b))
//copy(p, b)
select {
case <-w.closing:
return 0, errors.New("writer is closing")
default:
w.pending.Add(1)
defer w.pending.Done()
}
w.queue <- p
return len(p), nil
}
func (w *Writer) Close() error {
close(w.closing)
w.pending.Wait()
close(w.queue)
<-w.done
if w.f != nil {
if err := w.closeCurrentFile(); err != nil {
return err
}
}
return nil
}
func (w *Writer) listen() {
for b := range w.queue {
if w.f == nil {
if err := w.rotate(); err != nil {
w.logger.Println("Failed to create log file", err)
}
}
size := int64(len(b))
if _, err := w.bw.Write(b); err != nil {
w.logger.Println("Failed to write to file.", err)
}
w.bytesWritten += size
}
close(w.done)
}
func (w *Writer) closeCurrentFile() error {
if err := w.bw.Flush(); err != nil {
return errors.New("failed to flush buffered writer")
}
if err := w.f.Sync(); err != nil {
return errors.New("failed to sync current log file")
}
if err := w.f.Close(); err != nil {
return errors.New("failed to close current log file")
}
w.bytesWritten = 0
return nil
}
func (w *Writer) rotate() error {
if w.f != nil {
if err := w.closeCurrentFile(); err != nil {
return err
}
}
path := filepath.Join(w.opts.Directory, w.opts.FileNameFunc())
f, err := newFile(path)
if err != nil {
return errors.New("failed to create new file")
}
w.bw = bufio.NewWriter(f)
w.f = f
w.bytesWritten = 0
return nil
}
func New(logger *log.Logger, opts Options) (*Writer, error) {
if _, err := os.Stat(opts.Directory); os.IsNotExist(err) {
if err := os.MkdirAll(opts.Directory, os.ModePerm); err != nil {
return nil, errors.New("directory does not exist and could not be created")
}
}
w := &Writer{
logger: logger,
opts: opts,
queue: make(chan []byte, 2000),
closing: make(chan struct{}),
done: make(chan struct{}),
}
go w.listen()
return w, nil
}
func newFile(path string) (*os.File, error) {
return os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
}
func test(wg *sync.WaitGroup, w *Writer) {
i := 0
var s2 string
for i < 1 {
s2 = "abcdefg"
fmt.Println(s2)
fmt.Fprint(w, s2)
i++
}
wg.Done()
}
func FileNameFunc() (string) {
t := time.Now()
return fmt.Sprintf("%04d%02d%02d_%02d%02d%02d.log",
t.Year(),
t.Month(),
t.Day(),
t.Hour(),
t.Minute(),
t.Second(),
)
}
func main() {
var wg sync.WaitGroup
logger := log.New(os.Stderr, "logrotate", 0)
dir, err := os.Getwd()
if err != nil {
fmt.Println(err)
return
}
opts := Options{
Directory: dir,
FileNameFunc: FileNameFunc,
}
writer, err := New(logger, opts)
if err != nil {
logger.Println(err)
return
}
max := 2000
i := 0
for i < max {
wg.Add(1)
go test(&wg, writer)
i++
}
wg.Wait()
}
這是測試的結果。
================== WARNING: DATA RACE Read at 0x00c00000a523 by goroutine 7: runtime.slicecopy() c:/go/src/runtime/slice.go:197 +0x0 bufio.(*Writer).Write() c:/go/src/bufio/bufio.go:635 +0x3e5 main.(*Writer).listen() E:/test.go:77 +0x1ab Previous write at 0x00c00000a523 by goroutine 185: runtime.slicestringcopy() c:/go/src/runtime/slice.go:232 +0x0 fmt.(*buffer).writeString() c:/go/src/fmt/print.go:82 +0x107 fmt.(*fmt).padString() c:/go/src/fmt/format.go:110 +0x6f fmt.(*fmt).fmtS() c:/go/src/fmt/format.go:359 +0x75 fmt.(*pp).fmtString() c:/go/src/fmt/print.go:447 +0x1ba fmt.(*pp).printArg() c:/go/src/fmt/print.go:698 +0xdcf fmt.(*pp).doPrint() c:/go/src/fmt/print.go:1161 +0x12c fmt.Fprint() c:/go/src/fmt/print.go:232 +0x6c main.test() E:/test.go:154 +0x130 Goroutine 7 (running) created at: main.New() E:/test.go:138 +0x29b main.main() E:/test.go:190 +0x271 Goroutine 185 (finished) created at: main.main() E:/test.go:200 +0x2e0 ================== ================== WARNING: DATA RACE Read at 0x00c00008b1bd by goroutine 7: runtime.slicecopy() c:/go/src/runtime/slice.go:197 +0x0 bufio.(*Writer).Write() c:/go/src/bufio/bufio.go:635 +0x3e5 main.(*Writer).listen() E:/test.go:77 +0x1ab Previous write at 0x00c00008b1bd by goroutine 702: runtime.slicestringcopy() c:/go/src/runtime/slice.go:232 +0x0 fmt.(*buffer).writeString() c:/go/src/fmt/print.go:82 +0x107 fmt.(*fmt).padString() c:/go/src/fmt/format.go:110 +0x6f fmt.(*fmt).fmtS() c:/go/src/fmt/format.go:359 +0x75 fmt.(*pp).fmtString() c:/go/src/fmt/print.go:447 +0x1ba fmt.(*pp).printArg() c:/go/src/fmt/print.go:698 +0xdcf fmt.(*pp).doPrintln() c:/go/src/fmt/print.go:1173 +0xb4 fmt.Fprintln() c:/go/src/fmt/print.go:264 +0x6c fmt.Println() c:/go/src/fmt/print.go:274 +0xc0 main.test() E:/test.go:153 +0x42 Goroutine 7 (running) created at: main.New() E:/test.go:138 +0x29b main.main() E:/test.go:190 +0x271 Goroutine 702 (running) created at: main.main() E:/test.go:200 +0x2e0 ================== Found 2 data race(s) exit status 66
從上面我們可以看到,不管是 fmt.Println 或是 fmt.Fprintf,都有機會引起 data race!
目前我的解決方式是把 buffer 先複製起來,再傳進 logrorate 的 channel 裡。
copy(p, b)
2021年2月4日 星期四
Go Modules
A module is a collection of Go packages stored in a file tree with a go.mod file at its root.
go 1.14
require github.com/buger/jsonparser v1.1.1
require github.com/go-resty/resty/v2 v2.4.0
2021年1月22日 星期五
GUI app on Kobo Clara HD (04)
還是不想浪費 Golang 跨平台的能力,再加上不想花費太多時間,故想嘗試 HTML Golang GUI 的解決方式。
這次選擇的是 gowut 這個專案,無奈在 Clara HD 實機上測試時,還是無法正常執行。
第一個問題是它無法自動帶出瀏覽器視窗,這個尚屬合理。
第二個問題是即使自己開啟瀏覽器輸入網址,還是無法連上 gowut Server。
第三個問題是我改用 gowut 自己提供的測試網址,雖然看起來 GUI 元件有正常運作,但一來速度太慢,二來是多操作幾下就會整個無回應,三來是畫面會因為重繪而閃爍(電子紙特性)。
目前看來 HTML GUI 解決方式不是正解,光畫面會閃爍就無法讓人接受,不過這也延伸出另外一個問題,即使我使用 Qt 撰寫 GUI App,我一樣會遭遇畫面閃爍的問題,除非 Kobo 本身有提供 SDK 讓使用者呼叫暫時停止重繪,好避免畫面閃爍。
感覺 GUI App 這個問題快走入死胡同了?
2021年1月8日 星期五
GUI app on Kobo Clara HD (02)
Fyne 缺少的 libGL.so.1 確定是無法靜態連結了,至少就我目前看到的資料是如此。
在不考慮 GUI app 的情況下,其實寫個 Golang app 是最快的,在 Wi-Fi 開啟時就自動同步 Google Drive,就像 Kindle 同步個人文檔一樣,第一次時也是全部下載,並沒有讓使用者選擇,只是之後還是可以任意刪除再下載,甚至回過頭來刪除 Kindle Server 的個人文檔。
我想像中的行為大概是這樣:
2. 下次同步會檢查是否有下載過,已下載過的不會再重新下載。
2021年1月5日 星期二
GUI app on Kobo Clara HD (01)
昨天下班前終於開始了我計劃的第一步,首先便是嘗試 Fyne 這個 Golang 專案在跨平台編譯後是否能順利的跑在 Clara HD 上。
一開始便照著指引在 Linux 上準備 docker 環境,只要使用 go get github.com/fyne-io/fyne-cross,便可以自動下載 docker 所需要的環境,當然系統還是要先有 docker 本體。
回家後使用手機的 OTG 功能,修改我的 Kobo 翻頁器 script,直接啟動編譯好的執行檔。很可惜的是,無法順利啟動,從 log 中看到是缺少了 libGL.so.1 這個函式庫。
接下來便是研究是否可以使用靜態連結解決?也許這條路行不通也說不一定?
2020年9月18日 星期五
Golang filepath.Walk 行為及注意事項
某些情況下我們需要去解析目錄下含子目錄共有多少檔案,一般來說是使用遞迴,不同程式語言有不同的名稱,可能叫 parseDir 或是 walkThrough 等。
Golang 在 path/filepath 這個 package 裡面就有提供類似的功能,其用法為 filepath.Walk,我們需要傳入 2 個參數:
1. 要解析的目錄名稱。
2. WalkFunc 型式的函數,定義如下。
type WalkFunc func(path string, info os.FileInfo, err error) error
比較特別的是,我們在裡面不需要再寫出類似遞迴呼叫的語法,Golang 本身會一直呼叫我們傳進去的 WalkFunc 直到結束。
另外,也可以傳入 UNC 路徑,這邊要注意一點,由於微軟檔案長度 255 字元的限制,如果我們傳入的路徑裡面有超出長度的檔案,此時會回傳 error,故後面就會停止 parsing,錯誤訊息如下。
CreateFile ERROR_FILE_NAME: The system cannot find the path specified.
因此,如果我們還是要繼續 parsing,可以選擇忽略這個錯誤而直接回傳 nil。
2020年7月17日 星期五
Kobo Clara HD 翻頁器 DIY
Golang Cross-Compile 方式 on Windows
SET PATH=%PATH%;C:\Go\bin
SET GOPATH=%CD%
SET GOOS=linux
SET GOARCH=arm
SET GOARM=7
原本以為觸發 Linux Mouse Event 是一件很簡單的事,
幸好之前就有想到一招,就是在組 KoboRoot.tgz 時,裡面預留一道指令,把相關程式搬到 Kobo 顯示的電腦磁碟機內,則不論我是要抽換 HTTPServer 程式,或是要測試一些指令都會很方便,之後也可以如法泡製 udev 的 Script ,目前先留一個測試 Script 方便我除錯就好。
struct input_event {
struct timeval time;
unsigned short type;
unsigned short code;
unsigned int value;
};
2018年10月18日 星期四
go-seo SEO 小工具
故研究了一下要如何實現
後來發現只要下的關鍵字可以在 google 結果中找到 blog 頁面
這時再去點取該 blog 頁面
Blogger 系統便會在該 blog 頁面瀏覽次數加 1
於是就達成了我的目標
如此一來,我最受歡迎的頁面應該就不是大台北行政區圖了吧
相關程式請詳
https://github.com/tylpk1216/go-seo
2018年5月8日 星期二
Good Golang timing
寫了一個 timer 的範例
看了這個 code 才知道,自己跟高手的差距有多大
至少我從來沒想到閉包可以這樣用