Go言語の並列実行について学んだメモです。
func goroutine(s string, wg *sync.WaitGroup){
defer wg.Done()
for i := 0; i < 5; i++{
fmt.Println(s)
}
}
func normal (s string){
for i := 0; i < 5; i++{
fmt.Println(s)
}
}
func main(){
var wg sync.WaitGroup
wg.Add(1)
go goroutine("world", &wg)
normal("hello")
wg.Wait()
}
並列実行は、time.Sleepしないと並列実行している関数が実行される前にmain関数が終了しプログラムが終わることがある。それを防ぐためにsync.WaitGroupを使う wg.Add(1)をしたら同じ数wg.Done()を呼ばないとエラーになる
goroutineを使って並列実行するが、これらは別のプログラムなのでreturnで値を返せない。そのため、channelを使って同期させる。
func goruoutine1(s []int, c chan int){
sum := 0
for _, v := range s{
sum += v
}
c <- sum
}
func main(){
s := []int{1, 2, 3, 4, 5}
c := make(chan int)
go goroutine1(s, c)
x := <-c
fmt.Println(x)
}
mainからスライスのsとchanのcを送って、goroutineが走る。x:=<-cの部分でgoroutineのc<-sumが終わるまで止まる。 chanを複数作ってもいいし、1つのchanを複数の関数で使ってもよい。順番はキューの順番になる。
func main(){
ch := make(chan int, 2)
ch <- 100
fmt.Println(len(ch))
ch <- 200
fmt.Println(len(ch))
ch <-300
fmt.Println(len(ch))
}
はエラーになる。make(chan int, 2)の2はバッファで、chの最大個数を示す。 よって、2で定義したのに3つ目を入れようとしたのでエラーになる。 取り出しているとエラーにならない。取り出すのは
s := <- ch
という記法。
func main()){
ch := make(chan int, 2)
ch <- 100
fmt.Println(len(ch))
ch <- 200
fmt.Println(len(ch))
close(ch)
for c := range ch{
fmt.Println(c)
}
}
chanを取り出すときにfor range文を使いたいが、close(ch)を書かずに使うとエラーになる。バッファの個数以上取り出そうとするため。close(ch)を書いていればエラーにならない
func goruoutine1(s []int, c chan int){
sum := 0
for _, v := range s{
sum += v
}
c <- sum
}
func main(){
s := []int{1, 2, 3, 4, 5}
c := make(chan int)
go goroutine1(s, c)
x := <-c
fmt.Println(x)
}
これの、計算の途中過程を逐次表示するようにしたいなら、
func goruoutine1(s []int, c chan int){
sum := 0
for _, v := range s{
sum += v
c <- sum
}
close(c)
}
func main(){
s := []int{1, 2, 3, 4, 5}
c := make(chan int, len(s))
go goroutine1(s, c)
for i := range c{
fmt.Println(i)
}
}
close(c)はエラー防止。chanの数をメモリに確保したいならmake(chan int, len(s))とする。
func producer(ch chan int, i int){
//Something
ch <- i * 2
}
func consumer(ch chan int, wg *sync.WaitGroup){
for i := range ch{
func (){
defer wg.Done()
fmt.Println("process", i * 1000)
}()
}
}
func main(){
var wg sync.WaitGroup
ch := make(chan int)
//producer
for i := 0; i < 10; i++{
wg.Add(1)
go producer(ch, i)
}
//consumer
go consumer(ch, &wg)
wg.Wait //すべてのWaitGroupが終わるまで待つ
close(ch)
}
close(ch)がこの位置にあるのは、consumerのforループがまだchanがあると思って待っているため。 それを終わらせるためにclose(ch)をする。
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
//time.tickとtime.Afterはchを返す関数。
for {
select {
case <-tick: //返ってくるchを変数に入れなくてもよい。入れると時刻も出る
fmt.Println("tick.")
case <-boom: //返ってくるchを変数に入れなくてもよい。入れると時刻も出る
fmt.Println("BOOM")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
fmt.Println("#########")
}
for select文は、複数のチャネルを受信してチャネルによって処理を変えられる。selectは、for文の中にあるからずっと回る。for文がないと止まる。終わりたいなら、for文をbreakする 最後のfmt.Printlnはこのままだと実行されない。先にmain関数が終わってしまうため。実行したいなら、
OuterLoop2
for{
select{
case <- tick://返ってくるchを変数に入れなくてもよい。入れると時刻も出る
fmt.Println("tick.")
case <-boom://返ってくるchを変数に入れなくてもよい。入れると時刻も出る
fmt.Println("BOOM")
break OuterLoop2
default:
fmt.Println(" .")
time.Sleep(50*time.Millisecond)
}
}
fmt.Println("#########")
のようにして、ラベルを使ってfor文の上に出る。名前は何でもいい。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
c := make(map[string]int)
go func() {
for i := 0; i < 10; i++ {
c["key"] += 1
}
}()
go func() {
for i := 0; i < 10; i++ {
c["key"] += 1
}
}()
time.Sleep(1 * time.Second)
fmt.Println(c, c["key"])
}
このコードだと、成功したりエラーを出したりする。両方のゴルーチンがcへの書き込みをしており、cへの書き込みが同時になるとエラーになる。よって、sync, mutexを使う。
package main
import (
"fmt"
"sync"
"time"
)
type Counter struct {
v map[string]int
mux sync.Mutex
}
func (c *Counter) Inc(key string) {
c.mux.Lock()
defer c.mux.Unlock()
c.v[key]++
}
func (c *Counter) Value(key string) int {
c.mux.Lock()
defer c.mux.Unlock()
return c.v[key]
}
func main() {
c := Counter{v: make(map[string]int)}
go func() {
for i := 0; i < 10; i++ {
//c["key"] += 1
c.Inc("Key")
}
}()
go func() {
for i := 0; i < 10; i++ {
//c["key"] += 1
c.Inc("Key")
}
}()
time.Sleep(1 * time.Second)
fmt.Println(c, c.Value("Key"))
}