2021年3月30日 星期二
load average on linux
2021年3月27日 星期六
《蜀山劍俠傳》開書實測
有時候為了驗證電子書閱讀器的效能或功能,常常會買些原本不會看的書,上一次為了這個目的買的是《鋼鐵德魯伊》一書。
最近看到網路上有新舊讀墨小六的開書速度影片,原本以為因為這本書的字數關係,其他家的開書速度應該也會受影響,但據我實測的結果兩家 K 家閱讀器都是秒開,而我最愛的 Kindle Paperwhite 3 還是沒懸念的拿下冠軍寶座,我的最愛果然還是打遍天下無敵手。
2021年3月26日 星期五
程式設計師最強裝備 — 心流頭盔
以 RPG 冒險遊戲的概念來看,程式設計師的最強頭部裝備一定是心流頭盔無誤!
就以我來說,上星期已經完成這一季的所有 KR 項目,故這星期已經開始提前開發下一季的 KR 項目。截至昨天為止也才不過四天,但我已經 KO 了我預計要完成的 3 項 KR 項目,而其中的 80 % 都是集中在最後兩天。
我之所以能夠有如此神速的表現,除了我這一季的項目就已經做好基本的模組化以利後續重覆使用外,最主要的原因應該是我這兩天除了開會以外都維持在心流的狀態。
一開始先聽著蘇慧倫好聽的歌聲,不到 10 分鐘後,眼中、腦中、心中都只剩下一行又一行的程式碼,就在這個時刻,蘇慧倫已經遠離我好幾個光年,耳朵中完全感受不到任何聲音,時間彷彿靜止一樣。
我一定是個很幸運的人,恰巧我的工作就是我最喜歡的事。
心流頭盔入手
智力 + 50
敏捷 + 60
精神 + 100
2021年3月25日 星期四
購買繁體中文書這兩年半以來的回顧
2021年3月23日 星期二
目前手上的電子書五兄弟
原本的電子書四兄弟,在經歷了 Note Lite 掛點,Nova Pro 出售後,陸續又買了 3 台電子書閱讀器,故目前總共擁有電子書五兄弟。
2021年3月22日 星期一
Google Nest Mini 入手
init function in Go package
2021年3月20日 星期六
2021年3月8日 星期一
相容 mongoDB 的 memory DB - lungoDB
今天看到一個很神奇的專案,簡單來說就是一個相容 mongoDB 的 memory DB。
https://github.com/256dpi/lungo
第一個想到的用途就是可以更容易的避免使用 mongoDB 遇到的 GPL 問題。
假設我的專案是可用或可不用 mongoDB 的,則我的專案即使不使用 mongoDB,也保有功能上的完整性,也就是擁有獨立性與可區分性。故就算我使用了 mongoDB,我也不需要 open source。
自己的專案 open source 當然沒有什麼問題,但公司的專案就必須考慮清楚,有了這個專案,我就不用自己把 mongoDB 的 code 改成 SQLite 的 code。
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)