Golangによる並行処理
最近Golangを使って過去に書いたプログラムを書き直して勉強している。CライクでありCのめんどくさい記述が省けるとこに魅力を感じている。Golangは並行処理の記述が書きやすいらしい。では、手を出してみよう。ちなみに私は先輩にコードが汚い、センスがないと言われているので、見にくいかもです。これでも頑張ってます。
定積分の台形法による近似
今回Goroutineの練習に用いた題材は「定積分を台形法を用いて近似する」というもの。
アルゴリズムについては、ググってもらえばたくさん出てくる。いたってシンプルかつ簡単というのがGoroutine入門に向いているのではないか?的なノリで採択。
そして今回用いた数式はこちら。
\int_{0}^{1}\frac{4}{1+x^2}dx = \pi
計算の方針として、積分区間を任意の幅で分割し台形の計算を並行処理する。
日本語が下手くそすぎる故、コードをみてもらった方が早いです。
package main
import (
"fmt"
"sync"
)
const (
p_num = 15 //区切りはば
low = 0.0 //積分区間
high = 1.0
)
func main() {
ch := make(chan float32)
var dx float32
var x, sum float32
var wg sync.WaitGroup
point := make([]float32, p_num)
dx = (high - low) / p_num
x = low
for i := 0; i < p_num; i++ { //区切った時のXの値を記憶
point[i] = x
x += dx
}
for i := 0; i < p_num; i++ {
wg.Add(1)
go calculate(ch, i, point[i], dx, &wg)
//go func(i int){
// wg.Add(1)
// fmt.Println(i)
// x := point[i]
// gx := 4 / (1.0 + x*x)
// hx := 4 / (1.0 + (x+dx)*(x+dx))
// ch <- (gx + hx) * dx / 2
// wg.Done()
//}(i)
}
go func(){
wg.Wait()
close(ch)
}()
for v := range ch {
sum += v
}
fmt.Println(sum)
}
func calculate(ch chan float32, i int, x, dx float32, wg *sync.WaitGroup) {
gx := 4 / (1.0 + x*x)
hx := 4 / (1.0 + (x+dx)*(x+dx))
ch <- (gx + hx) * dx / 2
wg.Done()
}
今回はmain関数の外に別でcalculate関数を用意しているが、コメントアウト部分のように、mainの中に並行処理部分を記述しても良い。しかし、注意する点として、変数iをgoroutineに参照させるために
go func(i int){
// do something
}(i)
と書く必要がある。
仮に書かなかったら、どうなるかをわかりやすい例で書くと
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 10; i++ {
go func(){
fmt.Println(i)
}()
}
time.Sleep(time.Second*5) //goroutineが始まる前にmainが終了するのを防ぐため
//WaitGroupeを用いるのがオススメ
}
/**********************
10
8
10
10
10
10
10
10
10
10
**********************/
うまく変数iの値が参照されない。iを参照するように変更すると
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 10; i++ {
go func(i int){
fmt.Println(i)
}(i)
}
time.Sleep(time.Second*5) //goroutineが始まる前にmainが終了するのを防ぐため
//WaitGroupeを用いるのがオススメ
}
/**********************
1
0
7
6
8
9
3
5
4
2
**********************/
以上
はまったポイント
今回の練習ではまったポイントと言えば、WaitGroupeを用いて同期を取っているのだが、
全てのGoroutineがdeadlockしたよってpanicメッセージが飛んでくることがあった。これは、Wait()
もGoroutine化してあげることで解決。
go func(){
wg.Wait()
close(ch)
}()
全てのGoroutineが完了したらデータ受け渡し用のchannelをcloseしてあげることで、
for v := range ch {
sum += v
}
を用いてchannelの中の値をエラーなくrangeで参照することができる。
closeせずにchannelから値を参照しようとすると、エラーが発生する。これは少し手間取った。
以上
おわりに
次は、チャットアプリをTCPを用いてごにょごにょしているから、完成したら投稿しようと思う。