go言語の並行処理
概要
「Go言語による並行処理」を読んだのでメモ ※この本に書いてないことも以下では取り上げている
goroutineとsync
goroutineの書き方
一番単純なgoroutine(これは期待した通り動かない)
package main import "fmt" func main() { hello := func() { fmt.Println("hello") } go hello() }
無名関数helloを定義して、「go 関数()」で実行する しかしこれは実行しても何も出力されない
$go run ex1.go $
その理由は、main goroutineとhello goroutineはそれぞれ別個に動くので、 mainが終わる前に子の処理のhelloを待たないため。
そこで、意図したとおりにするためには、syncで待ち合わせをすることが一番簡単な実現方法となる
syncで待ち合わせをする(期待通りの挙動になる)
syncパッケージを使って待ち合わせをする場合は以下のようにする
- wg.Add(待ちたい処理の数)を定義
- hello goroutineが終わる前に、このgoroutineの終了を伝えるwg.Doneをdeferで実行
- hello goroutineが終わるまで、wg.Wait()で待つ
$cat ex2.go package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup wg.Add(1) hello := func() { defer wg.Done() fmt.Println("hello") } go hello() wg.Wait() }
これを実行すると以下の通り出力される
$go run ex2.go hello $
syncで待ち合わせをする(helloを定義しない)
上の例はより簡単に、以下のようにも書ける
- かっこの最後に 「 }()」がついていることに注意
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() fmt.Println("hello") }() wg.Wait() }
syncで待ち合わせをする(helloを通常の関数として定義する)
helloを通常の関数として定義する場合には以下のように、WaitGroupをポインタとして渡すことが必要になる
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup wg.Add(1) go hello(&wg) wg.Wait() } func hello(wg *sync.WaitGroup) { defer wg.Done() fmt.Println("hello") }
syncで複数の待ち合わせをする
waitNum := 3 と定義して、その回数分だけ待つ
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup waitNum := 3 wg.Add(waitNum) hello := func() { defer wg.Done() fmt.Println("hello") } for i := 0; i < waitNum; i++ { go hello() } wg.Wait() }
結果
hello hello hello
goroutineに引数を渡す
通常の関数と同様に、goroutineにも引数を渡せる
$cat ex3-2.go package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup waitNum := 3 wg.Add(waitNum) hello := func(i int) { defer wg.Done() fmt.Printf("hello %d\n", i) } for i := 0; i < waitNum; i++ { go hello(i) } wg.Wait() }
結果
$go run ex3-2.go hello 2 hello 0 hello 1
channel
最も簡単なchannel
一番簡単な例
- channelは複数のgoroutine間でデータを共有するためにあるので、ふつうこんな使い方はしない
package main import "fmt" func main() { ch := make(chan int, 1) // channelに5を送信 ch <- 5 // channelから整数値を受信 i := <-ch fmt.Println(i) }
実行結果
$go run chan.go 5
goroutineでのchannel
goroutineでchanelをやり取りする例
package main import "fmt" func main() { ch := make(chan int) go func() { // channelに5を送信 ch <- 5 }() // channelから5を受信 fmt.Println(<-ch) }
実行結果
5
複数のchannelを送受信することもできる
package main import "fmt" func main() { ch := make(chan int) n := 3 go func() { for i := 0; i < n; i++ { ch <- i } }() for i := 0; i < n; i++ { fmt.Println(<-ch) } }
実行結果
0 1 2
for rangeを使ったchannelの受信
for rangeを使ってchannelを受信する書き方もできる
ただし、for rangeを使って受信する場合、channelの終了がわからず永遠に待ち続けるので、goroutine側がこれ以上channelに送信しなくなった時点でchannelをcloseすることが必要
package main import "fmt" func main() { ch := make(chan int) n := 3 go func() { defer close(ch) // channelの終了を送信 for i := 0; i < n; i++ { ch <- i } }() for i := range ch { fmt.Println(i) } }
実行結果
0 1 2
deferを忘れると、待ち続けてdeadlockになる
deferをつけない場合の実行結果
0 1 2 fatal error: all goroutines are asleep - deadlock!
channelのcloseを確認する
channelからは、2番目の返り値としてchannelがclose済みかそうでないかを表すbool値が取得できる 慣習的にこの2番目の返り値はokとする
上の例で言えば、chから得られた値がi、chがclose済みかどうかがok(closeされていればfalse)となる。このokはとっても取らなくてもいい
i, ok := <-ch
参考 - A Tour of Go
上の例を、okがfalseとなるまでchの中身を出力し続けるようにした場合、以下のようになる
- ※上のfor rangeを使った書き方より冗長だしミスると無限ループになるからめったに使う必要ないと思う
package main import "fmt" func main() { ch := make(chan int) n := 3 go func() { defer close(ch) // channelの終了を送信 for i := 0; i < n; i++ { ch <- i } }() for { i, ok := <-ch if !ok { fmt.Println("ch closed") return // このreturnを忘れると無限ループになる } fmt.Println(i) } }
実行結果
0 1 2 ch closed
bufferなしchannel
bufferなしchannelとbufferつきchannelの挙動の違いについてシンプルにr例示している資料が意外とすぐに見つからなかった
まずbufferなしchannelの例
package main import ( "fmt" "time" ) func main() { ch := make(chan int) go func() { defer close(ch) defer fmt.Println("Producer Done") for i := 0; i < 5; i++ { ch <- i fmt.Printf("Sending: %d\n", i) } }() for i := range ch { fmt.Printf("Received %v\n", i) time.Sleep(1 * time.Second) } }
実行結果
Sending: 0 Received 0 Received 1 Sending: 1 Received 2 Sending: 2 Received 3 Sending: 3 Received 4 Sending: 4 Producer Done
- 1秒おきにSendingとReceivedが交互に出力されている
channelが満杯のときはchannelへの書き込みは(読み込みされるまで)ブロックされるので、bufferなしchannelは毎回読み込みをした後で追加の書き込みをしている
bufferつきchannel
bufferつきchannelは一度に送信できる件数を指定できる
package main import ( "fmt" "time" ) func main() { ch := make(chan int, 4) go func() { defer close(ch) defer fmt.Println("Producer Done") for i := 0; i < 5; i++ { ch <- i fmt.Printf("Sending: %d\n", i) } }() for i := range ch { fmt.Printf("Received %v\n", i) time.Sleep(1 * time.Second) } }
実行結果
Sending: 0 Sending: 1 Sending: 2 Sending: 3 Sending: 4 Producer Done Received 0 Received 1 Received 2 Received 3 Received 4
- buffer4に指定した場合、channelに対して先に4つ書き込みが終了し、あとからReceivedされていることがわかる
- このように、bufferつきchannelは読み込みを待つことなく何件まで書き込めるかを指定できる
select
selectは複数のcaseに対して、それぞれの条件が等しく選択されるように、疑似乱数を使って配分している。
例えば、以下のようにc1, c2という2つのチャネルから合計1000回読み込むとする。
- c1からのチャネルから読み込んだらc1Countを増やす
- c2からのチャネルから読み込んだらc2Countを増やす
package main import ( "fmt" ) func main() { c1 := make(chan interface{}) close(c1) c2 := make(chan interface{}) close(c2) var c1Count, c2Count int for i := 1000; i >= 0; i-- { select { case <-c1: c1Count++ case <-c2: c2Count++ } } fmt.Printf("c1Count: %d, c2Count: %d.\n", c1Count, c2Count) }
このコードを何回も実行した結果を見てみると、c1, c2がほぼ同じ回数ずつカウントされていることがわかる
$go run select2.go c1Count: 499, c2Count: 502. $go run select2.go c1Count: 498, c2Count: 503. $go run select2.go c1Count: 520, c2Count: 481. $go run select2.go c1Count: 513, c2Count: 488. $go run select2.go c1Count: 505, c2Count: 496. $go run select2.go c1Count: 502, c2Count: 499. $go run select2.go c1Count: 509, c2Count: 492. $
for-selectでdefault処理
以下のように、チャネルを待っている間やりたい処理をdefaultに書くことでgoroutineの結果を待っている間他の処理ができる
for { select { case <-チャネル: <チャネルを受け取ったあとの処理> case <-チャネル2: <チャネル2を受け取ったあとの処理> default: } <チャネル1とチャネル2を受け取る前に行いたい処理> }
for で無限ループをしながらselectでdoneが来るのを待ち続ける例
goroutineが5秒後にdoneにcloseを送るので、処理が終わる
それまではWaitを繰り返す
package main import ( "fmt" "time" ) func main() { done := make(chan interface{}) go func() { defer close(done) fmt.Println("Goroutine start") time.Sleep(5 * time.Second) fmt.Println("Goroutine end. send done") }() i := 0 for { select { case <-done: fmt.Println("Receive done") return default: } i++ fmt.Printf("Wait %d\n", i) time.Sleep(1 * time.Second) } }
これを実行すると以下のようになる
$go run sample.go Wait 1 Goroutine start Wait 2 Wait 3 Wait 4 Wait 5 Goroutine end. send done Receive done
ちなみに、以下のようにdefautlとselectの終端の}の間に処理を入れても同様の結果になるが、この処理が往々にして長くなることがあってselectの終端がわかりにくくなるので、自分はあまり好んで使う人はいないようだ
default: <チャネル1とチャネル2を受け取る前に行いたい処理> }
timeout
Go by Exampleに書いてあるtimeoutの方法
Go by Example: Timeouts に書いてある方法
time.After(タイムアウトする時間) を設定してselect文で受け取ることで、時間がかかるgoroutineのタイムアウト処理ができる
以下の例では、 - 2秒かかるgoroutineの結果c1が1秒のtimeout制限に引っかかってtimeout 1が出力され、 - 同じく2秒かかるgoroutineの結果c2が3秒のtimeout制限以内に終わるので、result 2が出力される
package main import ( "fmt" "time" ) func main() { c1 := make(chan string, 1) go func() { time.Sleep(2 * time.Second) c1 <- "result 1" }() select { case res := <-c1: fmt.Println(res) case <-time.After(1 * time.Second): fmt.Println("timeout 1") } c2 := make(chan string, 1) go func() { time.Sleep(2 * time.Second) c2 <- "result 2" }() select { case res := <-c2: fmt.Println(res) case <-time.After(3 * time.Second): fmt.Println("timeout 2") } }
実行結果
timeout 1 result 2
上のtimeout方法の問題点
上の方法には一つ問題があって、timeout 1と出てもgoroutineに切り出した処理は自動で止まらないということがある
ためしに以下のようにc1で受け取っていた部分を以下のようにtaskというgoroutineにして、task関数の中では0から999999まで数字を出力し続けるようにしてみる - また、最初と最後の時間差をとって、処理時間を計測してみる (結果は長くなるので注意)
package main import ( "fmt" "log" "time" ) func main() { start := time.Now() select { case res := <-task(): fmt.Println(res) case <-time.After(1 * time.Second): fmt.Println("timeout 1") } log.Printf("Total time: %fs", time.Since(start).Seconds()) } func task() <-chan string { res := make(chan string) go func() { defer close(res) for i := 0; i < 1000000; i++ { fmt.Println(i) time.Sleep(1 * time.Nanosecond) } res <- "done!" }() return res }
実行結果
0 1 2 (省略) 475135 475136 475137 timeout 1 2019/09/29 06:11:19 Total time: 12.296678s
おかしなことが起きた! 実行結果はマシンの性能にもよると思うが、「timeout 1」まで出した後でtimeoutが出力されて、「 Total time: 12.296678s」となっている。
しかも恐ろしいことに2回目に実行すると、今度は「timeout 1」すら出ずに12秒かかっている。
489587 489588 489589 2019/09/29 06:13:37 Total time: 12.209647s
3回目もまた結果が変わる
489490 489491 489492 2019/09/29 06:14:39 Total time: 12.120974s
timeoutとなっても、すでに起動してfmt.Println(i) を出力し続けるgoroutineは勝手に暴走して、それが終わるタイミングは全然予測できていない
これをtimeout時間の通り1秒で終了させたい場合は以下のようにcontextやdoneチャネルを使うといい。
timeoutでcontext cancelを行う場合
上のコードをcontextを用いて書き直したものが以下となる。
timeout時刻になるとcontextのcancel処理が飛んで、1秒で処理が終わっていることがわかる
package main import ( "context" "fmt" "log" "time" ) func main() { start := time.Now() ctx, cancel := context.WithCancel(context.Background()) defer cancel() res := task(ctx) select { case <-res: fmt.Println(res) case <-time.After(1 * time.Second): log.Println("timeout after 1s") cancel() log.Println(<-res) } log.Printf("Total time: %fs", time.Since(start).Seconds()) } func task(ctx context.Context) <-chan string { res := make(chan string) go func() { defer close(res) for i := 0; i < 1000000; i++ { select { case <-ctx.Done(): log.Printf("context cancelled. count: %d", i) res <- fmt.Sprintf("error: %s", ctx.Err()) return default: } fmt.Println(i) time.Sleep(1 * time.Nanosecond) } res <- "done!" }() return res }
実行結果
(省略) 38503 38504 38505 38506 38507 2019/09/29 06:30:35 timeout after 1s 2019/09/29 06:30:35 context cancelled. count: 37841 2019/09/29 06:30:35 error: context canceled 2019/09/29 06:30:35 Total time: 1.000472s
contextの使い方は公式は以下。ほかにもネット上にたくさんあるので、以下で簡単な説明だけ記載しておく。 Go Concurrency Patterns: Context - The Go Blog
簡単に説明
- コードの最初でcancel 機能付きのcontextを宣言する
- 「defer cancel()」によって、プログラム終了時に確実にすべてのcontextがキャンセルされるようにする
ctx, cancel := context.WithCancel(context.Background()) defer cancel()
- task関数にこのcontextを渡して(第一引数として渡すのがセオリー)、for 文の1ループごとに、「ctx.Done()」が来ていないかチェックしている
- このプログラムでは 「ctx.Done()」を受け取ったらlogに「context cancelled. count」とその時のループ番号を出力している
- さらにresにエラー原因として「 ctx.Err()」を返してreturnでgoroutineを終える
- このreturnがないとDoneを受け取ってもgoroutineが終わらないので必要
for i := 0; i < 1000000; i++ { select { case <-ctx.Done(): log.Printf("context cancelled. count: %d", i) res <- fmt.Sprintf("error: %s", ctx.Err()) return default: } fmt.Println(i) time.Sleep(1 * time.Nanosecond) }
- 呼び出し側のmain内では、「res := task(ctx)」としてtaskの戻り値(channel)をいったんres変数に定義
- time.Afterの制限時間内に処理が終われば、「case <-res」に入る
- time.Afterの制限時間を過ぎたら、「timeout after 1s」をlogに表示させて、contextのcancel関数を呼び出す(重要)
- 「log.Println(<-res)」には、task関数内で詰めたctx.Err()の内容が出力される「error: context canceled」
- 「<-res」とすることで、cancelをした後のtask関数が返すエラーをきちんと受け取ってから終わるように待ち合わせをしている
res := task(ctx) select { case <-res: fmt.Println(res) case <-time.After(1 * time.Second): log.Println("timeout after 1s") cancel() log.Println(<-res) }
contextを使うことで、goroutineを安全に終了させることができる
pipeline、fan-in、fan-out
go言語のpipeline、fan-in、fan-out - ludwig125のブログ
複数のgoroutineのエラーハンドリングをする
同時並列数を制御する
シグナル処理
GoogleAppEngineでgoroutineを使う
GoogleAppEngineでgoroutineを使った例 ludwig125.hatenablog.com
vmwareで「パワーオン中にエラーが発生しました」が出た(Windows10)
起きたこと
Windows10 で、 VMware Playerで以下のエラーが発生して起動しない
パワーオン中にエラーが発生しました:Transport(VMDB)error -44 Message. The VMware Authorization Service is not running.
やったこと
Windows10の場合、左下のWindowsボタンから「PC」を検索
PCを右クリックして「管理」を開く
「サービスとアプリケーション」を選択
「サービス」を選択
「VMware Authorization Service」をダブルクリック
「サービスの状態」の「開始」をクリック
OKを押して閉じると「VMware Authorization Service」が「実行中」になっていることが確認できる
go言語で呼び出す関数を動的に変更する
package main import ( "fmt" ) func main() { funcList := []func(){function1, function2, function3} for i:=0; i < 3; i++{ funcList[i]() } } func function1() { fmt.Println("print function1") } func function2() { fmt.Println("print function2") } func function3() { fmt.Println("print function3") }
実行結果
print function1 print function2 print function3
実行例 https://play.golang.org/p/MQnTJWIu7P1
参考にしたページ C# - 関数名call1, call2, call3...の連続実行をまとめる方法はありますか?|teratail
応用
上のを応用して自前のretry関数の挙動を確かめたもの
スプレッドシートへの書き込みを3回失敗するまでretryする関数を作りたかった
https://play.golang.org/p/HV0uqiVphap
以下は、3回失敗するまでretryをする関数とその実行例
- test1は、3回とも失敗するのでinsertedは0
- test1は、3回目で成功するのでinsertedは10
package main import ( "errors" "fmt" "time" ) type Resp struct { status int } func main() { target, inserted := retry([]func()(Resp, error){returnStatus1, returnStatus2, returnStatus3}) fmt.Printf("test1. target %d, inserted: %d", target, inserted) fmt.Printf("\n----------------------\n") target, inserted = retry([]func()(Resp, error){returnStatus1, returnStatus2, returnStatus4}) fmt.Printf("test2. target %d, inserted: %d", target, inserted) } func retry(returnStatusList []func()(Resp, error)) (int, int) { target_num := 10 fmt.Printf("insert target num: %v\n", target_num) var MaxRetries = 3 for attempt := 0; attempt < MaxRetries; attempt++ { resp, err := returnStatusList[attempt]() if err != nil { fmt.Printf("Unable to write value. %v. attempt: %d\n", err, attempt+1) time.Sleep(3 * time.Second) // 3秒待つ continue } status := resp.status if status != 200 { fmt.Printf("HTTPstatus error. %v. attempt: %d\n", status, attempt+1) time.Sleep(3 * time.Second) // 3秒待つ continue } // 書き込み対象の件数と成功した件数 fmt.Printf("succeded to write value.\n") return target_num, target_num } fmt.Printf("Failed to write data to sheet. reached to MaxRetries: %d\n", MaxRetries ) // 書き込み対象の件数と成功した件数 return target_num, 0 } func returnStatus1() (Resp, error) { resp := Resp{} resp.status = 200 err := errors.New("error! happend") return resp, err } func returnStatus2() (Resp, error) { resp := Resp{} resp.status = 404 return resp, nil } func returnStatus3() (Resp, error) { resp := Resp{} resp.status = 201 return resp, nil } func returnStatus4() (Resp, error) { resp := Resp{} resp.status = 200 return resp, nil }
結果
insert target num: 10 Unable to write value. error! happend. attempt: 1 HTTPstatus error. 404. attempt: 2 HTTPstatus error. 201. attempt: 3 Failed to write data to sheet. reached to MaxRetries: 3 test1. target 10, inserted: 0 ---------------------- insert target num: 10 Unable to write value. error! happend. attempt: 1 HTTPstatus error. 404. attempt: 2 succeded to write value. test2. target 10, inserted: 10
少し書き換え
上のはretryの中で回数を指定しているのが気に食わなかったのでちょっと修正。 getReturnStatusというラップ関数を用意
https://play.golang.org/p/hQK-TunSAWE
package main import ( "errors" "fmt" "time" ) type Resp struct { status int } func main() { target, inserted := retry() fmt.Printf("test1. target %d, inserted: %d", target, inserted) } func retry() (int, int) { target_num := 10 fmt.Printf("insert target num: %v\n", target_num) var MaxRetries = 3 for attempt := 0; attempt < MaxRetries; attempt++ { resp, err := getReturnStatus() if err != nil { fmt.Printf("Unable to write value. %v. attempt: %d\n", err, attempt+1) time.Sleep(3 * time.Second) // 3秒待つ continue } status := resp.status if status != 200 { fmt.Printf("HTTPstatus error. %v. attempt: %d\n", status, attempt+1) time.Sleep(3 * time.Second) // 3秒待つ continue } // 書き込み対象の件数と成功した件数 fmt.Printf("succeded to write value.\n") return target_num, target_num } fmt.Printf("Failed to write data to sheet. reached to MaxRetries: %d\n", MaxRetries ) // 書き込み対象の件数と成功した件数 return target_num, 0 } var count = 0 func getReturnStatus() (Resp, error) { funcList := []func()(Resp, error){returnStatus1, returnStatus2, returnStatus3} count++ return funcList[count-1]() } func returnStatus1() (Resp, error) { resp := Resp{} resp.status = 200 err := errors.New("error! happend") return resp, err } func returnStatus2() (Resp, error) { resp := Resp{} resp.status = 404 return resp, nil } func returnStatus3() (Resp, error) { resp := Resp{} resp.status = 201 return resp, nil } func returnStatus4() (Resp, error) { resp := Resp{} resp.status = 200 return resp, nil }
GoogleAppEngine(GAE)メモ
Tips
githubに上げたくない環境変数をどうやって管理するか
以下を参考に、別のyamlをincludesして、秘密にしたいinclude先のファイルは.gitignoreに入れてgithubには上げないようにする
app.yaml リファレンス | Python の App Engine スタンダード環境 | Google Cloud
こんな感じ app.yamlの中身
env_variables: HOGE: "hoge" FUGA: "fuga" includes: - secret_piyo.yaml
secret_piyo.yamlの中身
env_variables: PIYO: "piyo"
secretの方はgitignoreする
$cat .gitignore secret_piyo.yaml
トラブル対応
INVALID_ARGUMENT: Your app may not have more than 15 versions.
$gcloud app deploy app.yaml 略 ERROR: (gcloud.app.deploy) INVALID_ARGUMENT: Your app may not have more than 15 versions. Please delete one of the existing versions before trying to create a new version.
対応方法
$ gcloud app versions list
でバージョンのリストを表示
こんな感じに表示されるので、不要なVERSIONを選んで消す
$ gcloud app versions list SERVICE VERSION TRAFFIC_SPLIT LAST_DEPLOYED SERVING_STATUS default 20180811t000507 0.00 2018-08-11T00:05:29+09:00 SERVING default 20180812t234454 0.00 2018-08-12T23:47:48+09:00 STOPPED default 20180814t002149 0.00 2018-08-14T00:22:47+09:00 STOPPED default 20180814t004044 0.00 2018-08-14T00:41:48+09:00 STOPPED
上の2列目がバージョンID
以下のように消す
$gcloud app versions delete 20190224t071430 20190226t055739 ...以下複数まとめて消せる
deleteには結構時間がかかる
go言語アルゴリズムやTipsなどメモ
内容
go言語で自分がよく使う書き方やTipsのまとめ
並行処理関係は以下
BenchmarkTestは以下
Tips
入力値
入力値を受け取る
文字列で受け取る
var s string fmt.Scan(&s)
整数で受け取る
var N int fmt.Scan(&N)
複数の入力をまとめて受け取ることもできる
var a, b, c int fmt.Scan(&a, &b, &c)
空白区切りの入力値を受け取ってスライスにする
N文字の空白区切りの入力された数値をスライスにする
- fmt.Scanは、改行もしくは空白を区切ってしまうので、N回ループする
l := make([]int, N) for i := range l { fmt.Scan(&l[i]) }
20 24 59 ← 入力値 [20 24 59]
空白区切りの入力値を一行受け取ってスライスにする
参考: Goで標準入力から文字列や数値列を取得する - Qiita
package main import ( "bufio" "fmt" "os" ) func main() { s := bufio.NewScanner(os.Stdin) s.Scan() input := s.Text() l := strings.Split(input, " ") fmt.Println(l) }
3 42 25 ← 入力 [3 42 25] ← 全部stringなので注意
全部intのスライスにしたい場合はこんな感じかな
- この方法よりは上記のfmt.Scanを使ったほうがよっぼど簡単だと思う
package main import ( "bufio" "fmt" "os" "strconv" "strings" ) func main() { s := bufio.NewScanner(os.Stdin) s.Scan() input := s.Text() l := strings.Split(input, " ") toIntL := func(l []string) []int { var intL []int for _, v := range l { i, _ := strconv.Atoi(v) intL = append(intL, i) } return intL } fmt.Println(toIntL(l)) }
実行結果
431 12 64 ←入力値 [431 12 64]
整数の数列をソート
package main import ( "fmt" "sort" ) func main() { a := []int{10, 3, 4, 1} A := a sort.Ints(A) fmt.Println(A) B := a fmt.Println(sort.IntSlice(B)) // 上と同じ出力 C := a sort.Sort(sort.IntSlice(C)) fmt.Println(C) // 上と同じ出力 D := a sort.Sort(sort.Reverse(sort.IntSlice(D))) fmt.Println(D) // 逆順に出力 }
結果 [1 3 4 10] [1 3 4 10] [1 3 4 10] [10 4 3 1]
文字列を辞書順にソート
いったんスライスにしてソートしてからjoinで戻す方法
参考
package main import ( "fmt" "sort" "strings" ) func main() { var s string fmt.Scan(&s) sl := strings.Split(s, "") sort.Strings(sl) sj := strings.Join(sl, "") fmt.Println(sj) }
実行
gasdbfgaa ← 入力 aaabdfggs ← 出力
逆順にソートしたいときは以下を使う
- sort.Sort(sort.Reverse(sort.StringSlice(s)))
関数化したもの
複数条件で優先順位をつけたソート
安定ソートを数回するといい - 安定ソート(SliceStable)を使うことで、最初の並びをなるべくそのままにしつつ並べ替える
参考 - GoのSliceをSortする(sort.Sliceとsort.SliceStable) - Qiita
package main import ( "fmt" "sort" ) type Person struct { ID int Name string } func main() { group := []Person{ {ID: 2, Name: "A"}, {ID: 1, Name: "A"}, {ID: 3, Name: "A"}, {ID: 2, Name: "C"}, {ID: 1, Name: "B"}, {ID: 1, Name: "C"}, {ID: 3, Name: "C"}, {ID: 3, Name: "B"}, {ID: 2, Name: "B"}, } // groupをgroup1, group2に代入してそれぞれの変化を見る group1 := group // 最初にID順にソート sort.SliceStable(group1, func(i, j int) bool { return group1[i].ID < group1[j].ID }) // 次にName順にソート。この時、Nameが同じであれば上でソートされたID順の並びを崩さない(安定ソート) sort.SliceStable(group1, func(i, j int) bool { return group1[i].Name < group1[j].Name }) fmt.Printf("IDで安定SortしてからNameで安定ソート:%+v\n", group1) group2 := group // 最初にName順にソート sort.SliceStable(group2, func(i, j int) bool { return group2[i].Name < group2[j].Name }) // 次にID順にソート。この時、IDが同じであれば上でソートされたName順の並びを崩さない(安定ソート) sort.SliceStable(group2, func(i, j int) bool { return group2[i].ID < group2[j].ID }) fmt.Printf("Nameで安定SortしてからIDで安定ソート:%+v\n", group2) }
実行結果
IDで安定SortしてからNameで安定ソート:[{ID:1 Name:A} {ID:2 Name:A} {ID:3 Name:A} {ID:1 Name:B} {ID:2 Name:B} {ID:3 Name:B} {ID:1 Name:C} {ID:2 Name:C} {ID:3 Name:C}] Nameで安定SortしてからIDで安定ソート:[{ID:1 Name:A} {ID:1 Name:B} {ID:1 Name:C} {ID:2 Name:A} {ID:2 Name:B} {ID:2 Name:C} {ID:3 Name:A} {ID:3 Name:B} {ID:3 Name:C}]
数字の各桁の数値をすべて足す
方法1.
sum := 0 for n > 0 { sum += n % 10 n /= 10 }
⇒ n が 12345なら sは15
方法2.
sum := 0 for i := n; i > 0; i /= 10 { sum += i % 10 }
ポインタ
変数のポインタ
package main import ( "fmt" ) func main() { t := 10 fmt.Println(t) change := func(t *int) { *t = 11 } change(&t) fmt.Println(t) }
実行結果
10 11
グリッド
二次元配列を組み立て
package main import ( "fmt" "strings" ) func main() { var h, w int fmt.Scan(&h, &w) // high, width var p [][]string for i := 0; i < h; i++ { var l string fmt.Scan(&l) p = append(p, strings.Split(l, "")) } fmt.Println(p) fmt.Println(p[0][1]) }
実行結果
3 5 12345 67891 23456 [[1 2 3 4 5] [6 7 8 9 1] [2 3 4 5 6]] ← 出力 2 ← 出力
行列計算
n * m
の二次元配列を入力から受け取り
var n, m int fmt.Scan(&n) fmt.Scan(&m) a := make([][]int, n) for i := 0; i < n; i++ { a[i] = make([]int, m) for j := 0; j < m; j++ { fmt.Scan(&a[i][j]) } }
n * mの行列Aと m * l の行列Bの積としてn * lの行列Cを作成
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_D
var n, m, l int fmt.Scan(&n) fmt.Scan(&m) fmt.Scan(&l) a := make([][]int, n) for i := 0; i < n; i++ { a[i] = make([]int, m) for j := 0; j < m; j++ { fmt.Scan(&a[i][j]) } } b := make([][]int, m) for i := 0; i < m; i++ { b[i] = make([]int, l) for j := 0; j < l; j++ { fmt.Scan(&b[i][j]) } } c := make([][]int, n) for i := 0; i < n; i++ { c[i] = make([]int, l) } for k := 0; k < n; k++ { for j := 0; j < l; j++ { for i := 0; i < m; i++ { c[k][j] += a[k][i] * b[i][j] } } } fmt.Println(c)
構造体とメソッド
構造体
例
こんな感じにstructを宣言して初期化できる
package main import "fmt" type Person struct { name string age int weight float64 } func main() { person1 := Person{name: "Taro", age: 21, weight: 60.12} // フィールド名を指定するとわかりやすい fmt.Println(person1) person2 := Person{"Akira", 22, 61.12} // 順番通りなら指定しなくてもいい fmt.Println(person2) }
実行結果
{Taro 21 60.12} {Akira 22 61.12}
メソッド
goにはクラスはないが、構造体にメソッドを追加することでクラスメソッドのように使える
例
上で定義したPerson構造体の要素をすべてstringにしたスライスを返すメソッドを追加した例
func (p *Person) toString() []string { var str []string str = append(str, fmt.Sprintf("%s", p.name)) str = append(str, fmt.Sprintf("%d", p.age)) str = append(str, fmt.Sprintf("%f", p.weight)) return str } func main() { person1 := Person{name: "Taro", age: 21, weight: 60.12} fmt.Println(person1.toString()) }
実行結果
[Taro 21 60.120000]
例2
Person構造体のスライスPersonsを定義して、要素をすべてinterfaceの二次元配列にして返す関数を使ってみた例
type Person struct { name string age int weight float64 } type Persons []Person func (p *Person) toString() []string { var str []string str = append(str, fmt.Sprintf("%s", p.name)) str = append(str, fmt.Sprintf("%d", p.age)) str = append(str, fmt.Sprintf("%f", p.weight)) return str } func (p *Person) toInterfaceSlice() []interface{} { var is []interface{} is = append(is, p.name) is = append(is, p.age) is = append(is, p.weight) return is } func (ps *Persons) toInterfaceSlices() [][]interface{} { var iss [][]interface{} for _, p := range *ps { iss = append(iss, p.toInterfaceSlice()) } return iss } func main() { person1 := Person{name: "Taro", age: 21, weight: 60.12} fmt.Printf("要素が全部stringのスライスに変換 %v\n", person1.toString()) fmt.Printf("要素が全部interfaceのスライスに変換 %v\n", person1.toInterfaceSlice()) persons := Persons{} persons = append(persons, person1) person2 := Person{name: "Akira", age: 22, weight: 61.12} persons = append(persons, person2) fmt.Printf("要素が全部interfaceの二次元スライスに変換 %v\n", persons.toInterfaceSlices()) }
実行結果
要素が全部stringのスライスに変換 [Taro 21 60.120000] 要素が全部interfaceのスライスに変換 [Taro 21 60.12] 要素が全部interfaceの二次元スライスに変換 [[Taro 21 60.12] [Akira 22 61.12]]
コード https://play.golang.org/p/FeJx-_wXv68
Enum
参考
4 iota enum examples · YourBasic Go
エラーハンドリング
参考
【go】golangのエラー処理メモ - ②. 例外はないがエラーハンドリングはできるよ(インスタンスや型でハンドリング) - tweeeetyのぶろぐ的めも
エラー・ハンドリングについて(追記あり) — プログラミング言語 Go | text.Baldanders.info
Golangのエラー処理とpkg/errors | SOTA
Errors are values - The Go Blog ← 同じ関数を複数回呼ぶ時のエラーをまとめて扱う
reflect
こちらにまとめた ludwig125.hatenablog.com
アルゴリズム
全N個のものをM個単位で処理する
以下のようなlistを指定の件数ずつ分割して処理したいとき
package main import ( "fmt" ) func main() { l := []int{1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10} for _, i := range l { fmt.Println(i) } } // 結果 /* 1 2 3 4 5 6 6 7 8 9 10 */
以下のようにしてみた
package main import ( "fmt" ) func main() { l := []int{1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10} chunkSize := 3 for start := 0; start < len(l); start += chunkSize { end := start + chunkSize if end > len(l) { end = len(l) } fmt.Println(l[start:end]) fmt.Println("---") } } // 結果 /* [1 2 3] --- [4 5 6] --- [6 7 8] --- [9 10] --- */
Ubuntuインストール後の設定メモ
前提
Windows7にUbuntuをインストールする手順メモ - ludwig125のブログ
Ubuntuでデスクトップのディレクトリ名を「Desktop」にする
参考: Ubuntuでデスクトップのディレクトリ名を「Desktop」にする - ぬいぐるみライフ?
端末エミュレータを開き,以下のコマンドを実行する.
$ LANG=C xdg-user-dirs-gtk-update ダイアログが表示されるので,「Don't ask me this again」をチェックし,「Update Names」をクリックすればOK.
すでに「デスクトップ」の中にファイルを作っている場合、上記のコマンドで日本語の「デスクトップ」ディレクトリが残ったまま「Desktop」が作られる
ショートカットキー
UbuntuTips/Desktop/KeyboardShortcutOnUnity - Ubuntu Japanese Wiki
Ubuntu 16.04 LTSで使えるキーボードショートカット一覧 - 旧ID:itiriのブログ
端末
端末のショートカットキー
コピーアンドペースト 端末の起動 Ctrl+Shift+n
コピー Ctrl+Shift+c
ペースト Ctrl+Shift+v
.bash_profile
参考: ユーザーの環境変数を設定するbashの設定ファイルと、カスタムプロンプトについて | OXY NOTES Linuxユーザのためのチップス: .bash_profileで環境変数を設定する。
[~/Documents ] $ cat ~/.bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # User specific environment and startup programs ENV=$HOME/.bashrc USERNAME="xxxx" PATH=$PATH:"/$HOME/bin" export USERNAME ENV PATH PS1="[\w ] $ " [~/Documents ] $
Ubuntu14で.bash_profileが認識されない 正しくは .profile
Ubuntu14.04で.bash_profileが読み込まれない(見つからない) - Qiita
bash - I cannot find .bash_profile in ubuntu - Ask Ubuntu
上記に.bash_profileの設定を書いたが、起動時に.pash_profileに書いた設定が読み込まれていないようだったので調査
ubuntu14では .profileが認識されているらしい
Ubuntu起動と同時に端末を開く
参考: Ubuntu起動と同時に自動でターミナルを開く - Qiita
Ubuntuのターミナルでタブを複数起動させて、タブごとに別サーバにログインしたい時 | shinodogg.com
自分の設定(gnome端末を使っていた時)
[~] $cat ~/.config/autostart/gnome-terminal.desktop [Desktop Entry] Exec=gnome-terminal --geometry=160x48+0+0 Type=Application [~] $
↑使う端末をterminatorに変えたのでこれになった - 以下の設定で保存すると、次回からUbuntuを開くときに横幅1200、縦幅960のterminatorのWindowが左上隅に表示される
[~] $cat ~/.config/autostart/terminator-terminal.desktop [Desktop Entry] Exec=/usr/bin/terminator --geometry=1200x960+0+0 Type=Application
git
gitインストール
参考: 第3回 さっそくGitを使ってみよう!:ハックガールズと学ぼう!ゼロから学ぶGit講座|gihyo.jp … 技術評論社
sudo apt-get install git [~ ] $ git config --global user.name "ludwig125" [~ ] $ git config --global user.name ludwig125 [~ ] $ git config --global user.email "XXXX@gmail.com" [~ ] $ mkdir ~/git [~ ] $ cd git/ [~/git ] $ ls [~/git ] $ ls [~/git ] $ git init Initialized empty Git repository in /home/XXXX/git/.git/ [~/git ] $ git status On branch master 最初のコミット nothing to commit (create/copy files and use "git add" to track) [~/git ] $
githubの登録
今さら聞けない!GitHubの使い方【超初心者向け】 | TechAcademyマガジン
githubリポジトリ作成
git pushに失敗
[~/git/work ] $ git push origin master Username for 'https://github.com': ludwig125 Password for 'https://ludwig125@github.com': remote: Invalid username or password. fatal: Authentication failed for 'https://github.com/lugwig125/work.git/' [~/git/work ] $
git へpushする方法
1.アクセストークンを使う
[Git][GitHub]GitHubにPushする際に認証失敗する | DevAchieve hubを2-factor authentication(2要素認証)有効でも使いたい - Qiita
以下のように「Personal access tokens」でアクセストークンを発行してコピーしておく
パスワード入力にアクセストークンを使ったらpushできるようになった
[~/git/work ] $ git push origin master Username for 'https://github.com': ludwig125 Password for 'https://ludwig125@github.com': Counting objects: 6, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (5/5), 438 bytes | 0 bytes/s, done. Total 5 (delta 0), reused 0 (delta 0) To https://github.com/lugwig125/work.git 3847557..36d73b5 master -> master [~/git/work ] $
2.SSH認証をする
アクセストークンは面倒なので、こちらのほうが便利
参考:
GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~ - Qiita SSHの公開鍵を作成しGithubに登録する手順 - mon_sat at Co-Edo(半年前の自分への教科書 / 別院) MacでGitHubを使う手順 – SSH Keysの登録 / リポジトリの作成 / 基本コマンド | maesblog
手順
=> Enter passphrase :ubuntuのパスワードを入力
=> この公開鍵をコピー
[~/git/work ] $ ssh-keygen -t rsa -C "gitに登録したメルアド" Generating public/private rsa key pair. Enter file in which to save the key (/home/XXX/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/XXX/.ssh/id_rsa. Your public key has been saved in /home/XXX/.ssh/id_rsa.pub. The key fingerprint is: ☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓☓ The key's randomart image is: +--[ RSA 2048]----+ | . +==. | | *OE+. | | .=+*= . | | . +o.= | | S... | | | | | | | | | +-----------------+ [~/git/work ] $ [~/git/work ] $ cat ~/.ssh/id_rsa.pub ssh-rsa ○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○ [~/git/work ] $ ssh -T git@github.com Warning: Permanently added the RSA host key for IP address '192.30.252.129' to the list of known hosts. Hi lugwig125! You've successfully authenticated, but GitHub does not provide shell access. [~/git/work ] $
SSHKey登録結果
c++を使えるようにする
g++のインストール
[~/git/work/src/bin ] $ sudo apt-get install g++ [~/git/work/src/bin ] $ g++ -v 省略 gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.1) [~/git/work/src/bin ] $
c++のコンパイル
参考: 第6回 LinuxでC/C++言語のコンパイルを試す(3ページ目) | 日経クロステック(xTECH)
caps lockをctrlにする手順
下記を参考にしたら、capslockが無効になったが、ctrlにはならなかった。
またいつか試してみる
$ sudo vi /etc/default/keyboard XKBOPTIONS="ctrl:nocaps"
追記 キーボードの CapsLock と Control を入れ替える – talkwithdevices.com
sudo dpkg-reconfigure keyboard-configuration
をして設定を更新必要があるらしいが、以下のような画面が出た。
自分のキーボードの種類がわからない。。あとで
参考:
Ubuntu日本語フォーラム / Ubuntu14.04 LTSでのCtrlとCapslockの入れ替えについて
[LXDE] Caps LockキーをCtrlキーにする(LXDE) - Life with IT
keyboard - How to map Caps Lock key to something useful? - Raspberry Pi Stack Exchange
CapsLockをCtrlにするまとめ - Λlisue's blog
キーボードの CapsLock と Control を入れ替える – talkwithdevices.com
Zsh
その他設定
https://ludwig125.hatenablog.com/entry/2020/03/19/043511
vmware playerとホストのWindowsの間のフォーカスを切り替える - ludwig125のブログ
4kディスプレイで仮想マシンのUbuntuの文字サイズを大きくする - ludwig125のブログ
トラブル対応
Ubuntu14.0.4でGoogleChromeが起動しない - ludwig125のブログ
scalaでjavaクラスを呼び出す
概要
仕事でScalaから別javaを呼び出すときに妙に悩んだのでメモ
以下の3パターンを列挙する
自分の状況
javaからjavaを使う
javaの場合はclass名とファイル名が同じでないといけないらしい
以下のファイルを用意する
[ludwig125 JavaJava]$ cat SampleClass.java public class SampleClass { public static String val = "hoge"; public static int add(int x, int y) { return x + y; } public static int sub(int x, int y) { return x - y; } } [ludwig125 JavaJava]$ [ludwig125 JavaJava]$ cat useSample.java public class useSample { public static void main(String[] args) { System.out.println(SampleClass.val); System.out.println(SampleClass.add(5, 3)); System.out.println(SampleClass.sub(5, 3)); } } [ludwig125 JavaJava]$
コンパイルして実行
[ludwig125 JavaJava]$ javac SampleClass.java useSample.java [ludwig125 JavaJava]$ java useSample hoge 8 2 [ludwig125 JavaJava]$
scalaからscalaを使う
以下のファイルを用意する
[ludwig125 ScalaScala]$ cat SampleClass.scala class S { val v = "hoge" def add(a: Int, b: Int): Int = a + b def sub(a: Int, b: Int): Int = a - b } [ludwig125 ScalaScala]$ [ludwig125 ScalaScala]$ cat useSample.scala object useS { def main(args: Array[String]): Unit = { val s = new S println(s.v) println(s.add(5, 3)) println(s.sub(5, 3)) } } [ludwig125 ScalaScala]$
コンパイルして実行
[ludwig125 ScalaScala]$ scalac SampleClass.scala useSample.scala [ludwig125 ScalaScala]$ scala useS hoge 8 2 [ludwig125 ScalaScala]$
特記事項
scalaの場合javaと違ってファイル名とクラス名を一致させる必要はない 「val」がscalaの予約後なのでvに変えたが、valのまま使う方法もあったはず
scalaの場合、scalacでファイル名を指定してコンパイルしたあと、scalaコマンドの実行対象はクラス名(この場合useS)であることに注意
scalaからjavaを使う
以下のファイルを用意する
[ludwig125 ScalaJava]$ cat SampleClass.java public class SampleClass { public static String v = "hoge"; public static int add(int x, int y) { return x + y; } public static int sub(int x, int y) { return x - y; } } [ludwig125 ScalaJava]$ [ludwig125 ScalaJava]$ cat useSample.scala object useS { def main(args: Array[String]): Unit = { println(SampleClass.v) println(SampleClass.add(5, 3)) println(SampleClass.sub(5, 3)) } } [ludwig125 ScalaJava]$
コンパイルして実行
javaとscalaを個別にコンパイルしたけど、まとめてやる方法があるのかどうかは確認してない
[ludwig125 ScalaJava]$ javac SampleClass.java [ludwig125 ScalaJava]$ scalac useSample.scala [ludwig125 ScalaJava]$ scala useS hoge 8 2 [ludwig125 ScalaJava]$