Goことはじめ3
第3章 型システム―型を用いた安全なプログラミング:はじめてのGo―シンプルな言語仕様,型システム,並行処理|gihyo.jp … 技術評論社
より学んだことメモ。
package main import ( "fmt" ) // // 型の拡張 // 同じ型でも違う意味を持つもの(同じintでもIDを表すものと優先度を表すものとか)を混同しちゃうとバグになるようなときに使う。 // type ID int type Priority int func ProcessTask(id ID, priority Priority) { fmt.Println(id, priority); } func RunProcessTask() { fmt.Println("型の拡張 ---------- ") var id ID = 3 var priority Priority = 5 // typeが違うのでコンパイルエラーする // ProcessTask(priority, id) // typeが適合するのでこっちは通る ProcessTask(id, priority) } // // 構造体 // type Task struct { ID int Detail string // パッケージ外にpublicな属性は先頭大文字 done bool // パッケージ内でprivateな属性は先頭小文字 } func RunTask() { fmt.Println("構造体 ---------- ") // Task構造体の初期化 // 値を与えなかった場合はゼロ値で初期化される var task Task = Task{ ID: 1, Detail: "buy the milk", done: true, } // 定義順に値を渡すことでフィールド名を省略 // var task Task = Task{1, "buy the milk", true} fmt.Println(task.ID) fmt.Println(task.Detail) fmt.Println(task.done) } // // ポインタ型 // 引数の方を*Taskとすることでポインタを渡せる // これで参照渡しができる // func Finish(task *Task) { task.done = true } func RunFinish() { fmt.Println("ポインタ型 ---------- ") // &Taskでポインタを渡せる task := &Task{done: false} Finish(task) fmt.Println(task.done) // true } // // 構造体の初期化 // コンストラクタがないのでNew*というメソッドを作って初期化するのが通例 // func NewTask(id int, detail string) *Task { // new()メソッドは // 構造体のフィールドをすべてゼロ値で初期化して返す var task *Task = new(Task) task.ID = id task.Detail = detail return task } func RunNewTask() { fmt.Println("構造体の初期化 ---------- ") task := NewTask(1, "buy the milk") fmt.Printf("%+v\n", task) } // // 構造体にメソッドを追加する // func (task Task) String() string { // レシーバのコピーがわたされるので // ここで値を変えてもオリジナルの値は変わらない str := fmt.Sprintf("%d) %s", task.ID, task.Detail) return str } func (task *Task) Finish() { // ポインタ渡しなのでオリジナルの値が変わる task.done = true } func RunTaskFunc() { fmt.Println("構造体のメソッド追加 ---------- ") task := NewTask(1, "buy the milk") fmt.Printf("%s\n", task) task.Finish() fmt.Printf("%s\n", task.done) } // // インターフェース // // インターフェイスの定義 // 単純なインターフェースは*erという名前をつけるのが通例 type Stringer interface { String() string } // 引数にインターフェースを指定することで // 必要なメソッドを実装していると判断する func Print(stringer Stringer) { fmt.Println(stringer.String()) } // 何も定義しないインターフェースを使うと // どのような型も渡せるメソッドを作れる func Do(e interface{}) { // do something } type StringStruct struct { Text string } func (ss *StringStruct) String() string { return ss.Text; } func NewStringStruct(text string) *StringStruct { return &StringStruct{ Text: text, } } func RunInterface() { ss := NewStringStruct("インターフェース ---------- ") Print(ss) } // // 型の埋め込み // 継承の代わりになるもの // 下記ではUserをUserTaskに埋め込む例 // type User struct { FirstName string LastName string } func (u *User) FullName() string { fullname := fmt.Sprintf("%s %s", u.FirstName, u.LastName) return fullname } func NewUser(firstName, lastName string) *User { return &User{ FirstName: firstName, LastName: lastName, } } type UserTask struct { ID int Detail string done bool *User // Userを埋め込む } func NewUserTask(id int, detail, firstName, lastName string) *UserTask { task := &UserTask { ID: id, Detail: detail, done: false, User: NewUser(firstName, lastName), } return task } func RunEmbed() { fmt.Println("型の埋め込み ---------- ") task := NewUserTask(1, "buy the milk", "Jxck", "Daniel") // UserTaskにUserのフィールドが埋め込まれている fmt.Println(task.FirstName) fmt.Println(task.LastName) // UserTaskにUserのメソッドが埋め込まれている fmt.Println(task.FullName()) // UserTaskから埋め込まれたUser自体にもアクセス可能 fmt.Println(task.User) } // // 型の変換 // func CastFunc() { // キャスト var i uint8 = 3 var j uint32 = uint32(i) // uint8 -> uint32 fmt.Println(j) var s string = "abc" var b []byte = []byte(s) // string -> []bytes fmt.Println(b) // キャストに失敗した場合はパニックが発生する // a := int("a") } func TypeAssertionFunc(value interface{}) { // TypeAssertion // 第1戻り値はその型に変換された値が返り、 // 第2戻り値は成否を返す // (第2戻り値を指定しなかった場合はパニック起こる) s, ok := value.(string) if ok { fmt.Printf("value is string: %s\n", s) } else { fmt.Printf("value is not string\n") } } func TypeSwitchFunc(value interface{}) { switch v := value.(type) { case string: fmt.Printf("value is string: %s\n", v) case int: fmt.Printf("value is int: %d\n", v) case Stringer: fmt.Printf("value is Stringer: %s\n", v) } } func RunType() { fmt.Println("型の変換 ---------- ") CastFunc() TypeAssertionFunc("StringValue") TypeAssertionFunc(1) TypeSwitchFunc("StringValue!!") TypeSwitchFunc(10) } func main() { RunProcessTask() RunTask() RunFinish() RunNewTask() RunTaskFunc() RunInterface() RunEmbed() RunType() }
チャネルにバッファを設けて処理ブロックを制御
package main import ( "fmt" "time" ) func main() { // バッファを3として設定 ch := make(chan string, 3) go func() { time.Sleep(time.Second) s := <-ch // 1秒後にデータを読み出す fmt.Printf("get %s\n", s) }() ch <- "a" // ブロックしない fmt.Println("put a") ch <- "b" // ブロックしない fmt.Println("put b") ch <- "c" // ブロックしない fmt.Println("put c") ch <- "d" // 1秒後にデータが読み出されるまでブロック fmt.Println("put d") }
geminaboxにgem登録する手順メモ
# 準備 gem install geminabox --no-ri --no-doc # Gemを作る gem build -V ***.gemspec mv ***-0.1.0.gem pkg/. # アップロードする gem inabox ./pkg/***-0.1.0.gem --host http://example.com
Goことはじめ2
第2章 基本文法―覚えやすいコンパクトな言語仕様:はじめてのGo―シンプルな言語仕様,型システム,並行処理|gihyo.jp … 技術評論社
より学んだことメモ。
基本的な文法について
package main import ( "fmt" "os" "errors" "log" // f "fmt" -> と記述すると f.Println()のようにアクセスできる // . "strings" -> と記述すると ToUpper('xx') とパッケージ指定なしでアクセスできる ) // // 代入処理について // func substitute() { fmt.Println("substitute -------------------- ") // 変数代入 var message string = "hello world" fmt.Println(message) // 関数の中では型推論して代入もできる message2 := "hello world" fmt.Println(message2) // 定数定義 const Hello string = "Hello Const" // 変数を宣言して明示的に初期化しなかった場合は // ゼロ値で初期化される // 整数型: 0 // 浮動小数点型: 0.0 // bool: false // string: "" // 配列: 各要素がゼロ値の配列 // 構造体: 各フィールドがゼロ値の構造体 // そのほかの型: nil const message3 string = ` 複数行に渡る 文字列を使うときは バッククォートでくるる ` } // // 条件分岐・繰り返し // func repeat() { fmt.Println("repeat -------------------- ") // if文 Goでは丸かっこは不要 a, b := 10, 100 if a > b { fmt.Println("a is larger than b") } else if a < b { fmt.Println("a is smaller than b") } else { fmt.Println("a equals b") } // 繰り返しはfor文だけ n := 0 for n < 10 { fmt.Printf("n = %d\n", n) n++ } // 無限ループ・break・continue m := 0 for { m++ if m > 10 { break // ループを抜ける } if m%2 == 0 { continue // 偶数なら次の繰り返しに映る } fmt.Println(m) // 奇数のみ表示 } // switch文 // breakは書かなくていい p := 10 switch p { case 15: fmt.Println("FizzBuzz") case 10: fmt.Println("Buzz") case 3, 6, 9: // カンマ切りで複数指定可能 fmt.Println("Fizz") // そのまま下に処理をおろしたい場合は // fallthrough // と記述する(逆break) default: fmt.Println(p) } // if文と同じようにかける q := 10 switch { case (p % 15 == 0): fmt.Println("FizzBuzz") case (p % 5 == 0): fmt.Println("Buzz") default: fmt.Println(q) } } // // 関数 // // 引数の後に型を指定する func sum1(i, j int) { // func sum(i int, j int) と同じ fmt.Println(i + j) } // 戻り値がある場合は引数の次に指定する func sum2(i, j int) int { return i + j } // 関数は複数の値を返せる func swap(i, j int) (int, int) { return j, i } // 内部で発生したエラーは戻り値で表現する func errorFunc() { // _ で 第1戻り値を無視 _, err := os.Open("sample.txt") if err != nil { // エラー処理 fmt.Println("file open error") } fmt.Println("file open success") } // 自作のエラーはerrorsパッケージを用いて作ることができる func div(i, j int) (int, error) { if j == 0 { // 自作のエラーを返す return 0, errors.New("divied by zero") } return i / j, nil } // 名前付き戻り値 // 関数内ではゼロ値で初期化された変数として扱われる func divWithName(i, j int) (result int, err error) { if j == 0 { err = errors.New("divied by zero") return // return 0, err と同じ } result = i / j return // return result, nil と同じ } // 関数はオブジェクト func wrapFunc() { // 関数リテラル(無名関数) // 即時実行される func(i, j int) { fmt.Println(i + j) }(2, 4) // 関数は変数に代入できる var sum func(i, j int) = func(i, j int) { fmt.Println(i + j) } sum(2, 3) } // // 配列 // func arrayFunc() { fmt.Println("arrayFunc -------------------- ") // Goの配列は固定長 var arr [4]string arr[0] = "a" arr[1] = "b" arr[2] = "c" arr[3] = "d" fmt.Println(arr[0]) // 宣言と代入 arr2 := [4]string{"a", "b", "c", "d"} fmt.Println(arr2) // 暗黙的なサイズ指定 arr3 := [...]string{"a", "b", "c", "d"} fmt.Println(arr3) // 長さの違う配列は別の型 // 関数に配列を渡す場合は"値渡し"になる } // // スライス // スライスは,可変長配列として扱うことができます。 // 配列を直接使うのは,シビアなメモリ管理が必要な一部のプログラムだけなので, // 基本的にはスライスを用います。 // func sliceFunc() { fmt.Println("sliceFunc -------------------- ") // スライスの型には長さの情報はない var s []string // 追加 s = append(s, "a") // 追加した要素を返す s = append(s, "b") s = append(s, "c", "d") fmt.Println(s) // [a b c d] // 結合 s1 := []string{"a", "b"} s2 := []string{"c", "d"} s1 = append(s1, s2...) // s1にs2を追加 fmt.Println(s1) // [a b c d] // 繰り返し処理 // rangeは配列,スライス,string,マップに対して使うことができる s11 := []string{"a", "b", "c", "d"} for i, v := range s11 { // i = 添字, v = 値 fmt.Println(i, v) } // 値の切り出し s21 := []int{1, 2, 3, 4, 5} fmt.Println(s21[2:4]) // [2 3] fmt.Println(s21[0:len(s)]) // [0 1 2 3 4 5] fmt.Println(s21[:3]) // [0 1 2] fmt.Println(s21[3:]) // [3 4 5] fmt.Println(s21[:]) // [0 1 2 3 4 5] // 可変長引数 var _sum func(nums ...int) (result int) = func(nums ...int) (result int) { // numsは []int型 for _, n := range nums { result += n } return } fmt.Println(_sum(1, 2, 3, 4)) // 10 } // // マップ // マップは値をKey-Valueの対応で保存するデータ構造 // func mapFunc() { fmt.Println("mapFunc -------------------- ") // 宣言 key=int, value=string var month map[int]string = map[int]string{} month[1] = "January" month[2] = "February" fmt.Println(month) // 宣言と初期化を一緒にする場合 month2 := map[int]string { 1: "January", 2: "February", } fmt.Println(month2) // 値を取り出す jan := month[1] fmt.Println(jan) // マップにキーが存在するかチェック _, ok := month[1] if ok { fmt.Println("month[1] is present") } // 要素の削除 delete(month, 1) // 繰り返し処理 // 処理順番は保証されない for key, value := range month { fmt.Printf("%d %s\n", key, value) } } // // ポインタ // ポインタを使うことで参照渡しができる // func callByValue(i int) { i = 20; // 値を上書きする } func callByRef(i *int) { *i = 20; // 参照先を上書きする } // // 関数を抜けるときに必ず実行 // func deferFunc() { fmt.Println("deferFunc -------------------- ") file, err := os.Open("./error.go") if err != nil { fmt.Println("FileOpenError") } // 関数を抜ける前に必ず実行される defer file.Close() // 正常処理 fmt.Println("deferFuncProcess") } // // パニックと後処理 // func panicFunc() { fmt.Println("PanicFunc -------------------- ") // 後処理 defer func() { // パニックで発生したエラーはrecoverで取得できる err := recover() if err != nil { log.Print(err) } }() a := []int{1, 2, 3} fmt.Println(a[10]) // パニック発生 // 自前でパニックを起こすこともできる // panic(errors.New("index out of range")) } func main() { substitute() repeat() fmt.Println("Function -------------------- ") sum1(1, 2) s := sum2(2, 3) fmt.Println(s) x, y := swap(3, 4) fmt.Println(x, y) errorFunc() _, err := div(10, 0) if err != nil { log.Print(err) // Fatalを使用するとステータス=1で終了する // log.Fatal(err) } m, err := divWithName(10, 2) fmt.Println(m) wrapFunc() arrayFunc() sliceFunc() mapFunc() fmt.Println("Pointer -------------------- ") var i int = 10; callByValue(i) // 値渡し fmt.Println(i) // 10 callByRef(&i) // 参照渡し fmt.Println(i) // 20 deferFunc() panicFunc() }
シェルのtestコマンド
シェルのtestコマンドの理解が怪しかったので、使い方をメモしておく。
#!bin/sh # $?==0 で正常終了 # 数値の比較 ## 1 == 1 test 1 -eq 1; echo $? ## 1 != 2 test 1 -ne 2; echo $? ## 1 < 2 test 1 -lt 2; echo $? ## 2 > 1 test 2 -gt 1; echo $? ## 1 <= 1 test 1 -le 1; echo $? ## 1 >= 1 test 1 -ge 1; echo $? # 文字列の比較 ## "abc" == "abc" test "abc" = "abc"; echo $? ## "abc" != "aaa" test "abc" != "aaa"; echo $? # 空チェック ## 変数に値が入っているか test -n HOGE; echo $? ## 変数が空か test -z HOGE; echo $? # ファイルチェック ## file2が新しいか touch file1 touch file2 test ./file1 -ot ./file2 ## fileが存在するか touch file3 test -f ./file3; echo $? ## fileが存在しない test ! -f ./file4; echo $? ## ディレクトリが存在するか mkdir dir1 test -d ./dir1; echo $? # 略式 [ ] # [ は testコマンド # ] は [を使うためのオプション [ 1 -lt 2 ]; echo $? # AND条件 OR条件 [ 1 -eq 1 ] && [ 1 -lt 2 ] || [ 2 -eq 2 ]; echo $? # if文 ## 一般的な書き方 if [ 1 -eq 1 ] && [ 1 -lt 2 ] || [ 2 -eq 2 ]; then echo "if文の中の実行" fi ## これでもOK if [ 1 -eq 1 ] && [ 1 -lt 2 ] || [ 2 -eq 2 ] then echo "if文の中の実行" fi
Goことはじめ
http://gihyo.jp/dev/feature/01/go_4beginners で学んだことをメモしていく。
はじめに
# インストール brew install go # ./bash_profileの設定 export GOROOT=/usr/local/opt/go/libexec export GOPATH=$HOME export PATH=$PATH:$GOROOT/bin:$GOPATH/bin # ソースコードから実行 go run hello.go # コンパイルして実行 go build hello.go ./hello # コーディング規約に合わせてフォーマット go fmt hello.go # パッケージ(ここではfmt)のドキュメントを表示 go doc fmt
プロジェクトを作る
# プロジェクトディレクトリ基本形 # Goのコマンドは,このGOPATHとその下にある先の3つのディレクトリの命名規則を用いて,Makefileなどの構成ファイルは一切なしで,依存関係を解決してビルドできるようになっている。 tree hello/ ├── bin ├── pkg └── src # プロジェクトディレクトリにパスを通す cd hello export GOPATH=`pwd` # helloをGOPATHに登録 # ソースを書く1 cat src/hello/hello.go # --- package hello var Message string = "hello world" # ソースを書く2 cat src/main/main.go # --- package main import ( "fmt" "hello" ) func main() { fmt.Println(hello.Message) // hello world } # 実行してみる cd $GOPATH/src/main go run main.go hello world # 実行ファイルをbin以下に作る cd $GOPATH/src/main go install # binにパスを通しておくと、すぐに実行できて便利 export PATH=$PATH:$GOPATH/bin main hello world
ActiveRecord::Base.transactionで並列で同じレコードを扱ったときの動きメモ
排他制御まわりがちゃんとわかっていなかったので整理。
Article.find(1).value # => 1 def inc row = Article.find(1) row.value = row.value + 1 row.save end # 同時並行処理する Thread.new { inc } Thread.new { inc } Article.find(1).value # => 2になるか,3になるかは神のみぞ知る
だけど、
Article.find(1).value # => 1 def inc ActiveRecord::Base.transaction do row = Article.find(1) row.value = row.value + 1 row.save end end # 同時並行処理する Thread.new { inc } Thread.new { inc } Article.find(1).value # => これなら3になるよね!たぶん
こうするとうまくいくはず。
検証
Article.find(1).value # => 1 def inc(wait1 = 0, wait2 = 0) sleep wait1 if wait1 > 0 ActiveRecord::Base.transaction do row = Article.find(1) row.value = row.value + 1 sleep wait2 if wait2 > 0 row.save! end end # 同時並行処理する Thread.new { inc(0, 10) } # ... t1 Thread.new { inc(5, 0) } # ... t2 # t1.transaction -> t1.find -> t2.transaction(待ちになる) -> # t1.save -> t1.commit -> t2.find -> t2.save -> t2.commit # になる想定。 Article.find(1).value # => 3にならず、2になってしまった...
だめじゃん。
検証2
悲観的ロックをかける
https://qiita.com/upinetree/items/b3329501561268f7678a
Article.find(1).value # => 1 def inc(wait1 = 0, wait2 = 0) sleep wait1 if wait1 > 0 ActiveRecord::Base.transaction do # SELECT * FROM "articles" FOR UPDATE row = Article.all.lock.find(1) row.value = row.value + 1 sleep wait2 if wait2 > 0 row.save! end end # 同時並行処理する Thread.new { inc(0, 10) } # ... t1 Thread.new { inc(5, 0) } # ... t2 # t1.transaction -> t1.find -> t2.transaction(待ちになる) -> # t1.save -> t1.commit -> t2.find -> t2.save -> t2.commit # になる想定。 Article.find(1).value # => 3になった!
これだとうまくいった。
最後に
トランザクションについて
トランザクションは、完全に排他してくれるわけではなく、あくまで、複数テーブルのUPDATEで「すべて更新される」か「すべて更新されないか」を保証するだけのもの。
トランザクション内でロックがかかった場合は、COMMITかROLLBACKされるまでロックは維持される。
DBレベルのロックについて(PostgreSQLの場合)
【テーブルレベルのロック】
ROW EXCLUSIVE
UPDATE、DELETE、およびINSERTコマンドは、(参照される他の全てのテーブルに対するACCESS SHAREロックに加えて)対象となるテーブル上にこのモードのロックを獲得します。 通常、このロックモードは、テーブルのデータを変更する問い合わせにより獲得されます。
→ つまり何も指定せずに、INSERT、UPDATE、DELETEすると、ROW EXCLUSIVEが自動でかかる。
【行レベルのロック】
FOR UPDATE
FOR UPDATEによりSELECT文により取り出された行が更新用であるかのようにロックされます。
→ 基本的にSELECTではロックはかからないけど、かけたければFOR UPDATE使う。
https://www.postgresql.jp/document/9.4/html/explicit-locking.html より
ロックについて
ロックには種類がある。
【楽観的ロック】
アプリケーションレベルのロック。
更新する直前に、対象レコードが取得時と変わっていないかを確認する。
【悲観的ロック】
DBレベルのロック。
レコード取得した時点で、対象レコードにロックをかける。
SELECT … FOR UPDATE を利用したもの。
→ 今回話しているのはこっちのロックの方。
補足
RedisObject使っているとクラスメソッドのlockが上書きされちゃうようなので、Article.lock... ではなく Article.all.lock... としたほうが無難っぽい。
https://github.com/nateware/redis-objects/blob/master/lib/redis/lock.rb#L36
シェルでクロス集計するメモ
クロス集計をBash(とawk)だけで実装した話 より学んだことメモ
処理の流れ
1.必要なカラムの抽出
2.クロス集計の行と列の要素を取り出す。
# クロス集計表の行になる部分を抽出してユニーク化&ソート cat table.csv | cut -f 1 -d , | sort -u > rowname.txt # クロス集計の列になる部分を抽出、改行をタブに変えて縦横変換 cat table.csv | cut -f 2 -d , | sort -u | gtr '¥n' '¥t' > header.txt # cutコマンド補足 cut -f {取り出したい項目} -d {区切り文字}
3.awkでファイル分割する。
cat table.csv a,ice,130 a,ice,180 b,juice,120 b,ice,130 i,oreo,210 i,oreo,210 i,oreo,210 # awkで分割した結果をファイル出力できる cat table.csv | gawk -F, '{print > "split_"$2}' ls split_ice split_juice split_oreo table.csv
4.awkで集計する
5.sortでソート
ls | grep "split_" | gsed -e 's/split_//' | while read file; do gawk -F, '{a[$1]+=$3;}END{for(i in a)print i","a[i];}' split_${file} | sort -k 1 > sort_${file}; done
とか
gawk '{print FILENAME","$0}' split_* | gsed 's/split_//' | gawk -F, '{a[$1","$2]+=$4}END{for(i in a)print i","a[i];}' | sort -t, -k1,2 | gawk -F, '{print $2","$3 > "sort_"$1}'
6.2で取り出した行の要素と5の結果をJOIN
# joinできなかったキーも含めて出力し、空行は0で埋める ls | grep sort_ | gsed 's/sort_//' | while read file; do join -t ',' -1 1 -2 1 -a 1 rowname.txt sort_${file} | gawk -F, '{print $2}' | gsed 's/^$/0/g' > split_sum_${file}; done cat split_sum_juice 0 120 0 # joinコマンドメモ # -t 区切り文字を指定 # -1 1番目のファイルで結合キーに使う項目番号 # -2 2番目のファイルで結合キーに使う項目番号 # -a 1 LEFT JOIN (未指定の場合はINNNER JOIN) # -a 2 RIGHT JOIN join -t ',' -1 1 -2 1 -a 1 rowname.text sort_${file}
7.paste と cat でマージしてリストを表に加工
# pasteで列同士を結合する cp header.txt sum_result.txt paste rowname.txt split_sum_* >> sum_result.txt