LoginSignup
92
77

More than 5 years have passed since last update.

Goのsyncパッケージで相互排他ロックを行う

Posted at

syncパッケージには、相互排他ロックがあります。
これを使うと意図的に処理にブロックを入れることが出来て
goroutineのチャンネルのやりとりのウェイトのようなことが実装出来ます。

Mutex型

相互排他ロック。

使わない場合

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan bool)

    for i := 0; i < 5; i++ {
        go func(i int) {
            time.Sleep(500 * time.Millisecond)
            fmt.Println(i)
            c <- true
        }(i)
    }
    for i := 0; i < 5; i++ {
        <-c
    }
}

結果は

0
4
3
2
1
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    m := new(sync.Mutex)
    c := make(chan bool)

    for i := 0; i < 5; i += 1 {
        m.Lock()
        go func(i int) {
            time.Sleep(500 * time.Millisecond)
            fmt.Println(i)
            m.Unlock()

            c <- true
        }(i)
    }
    for i := 0; i < 5; i++ {
        <-c
    }
}

結果

0
1
2
3
4

すでにLockされている場合は、呼び出したゴルーチンがロック可能になるまでそこでブロックされます。
Unlock()でロック解除。あるゴルーチンでロックして、別のゴルーチンでアンロックをかけても問題ないです。

ちなみにこれならばゴルーチンのみで書けます。
http://play.golang.org/p/8ilFwkIJx5

RWMutex型

reader/writerの相互排他チェックで使う。

func (rw *RWMutex) Lock() で書き込みのロック
func (rw *RWMutex) Unlock() で書き込みのアンロック

func (rw *RWMutex) RLock() で読み込みのロック
func (rw *RWMutex) RUnlock() で読み込みのアンロック

https://github.com/gorilla/context/blob/master/context.go#L14 などで使われています。

Once型

実行を1回だけ行うオブジェクトです。

http
package main

import (
     "fmt"
     "sync"
)

func main() {
     var once sync.Once
     onceBody := func() {
          fmt.Println("Only once")
     }
     done := make(chan bool)
     for i := 0; i < 10; i++ {
          go func() {
               once.Do(onceBody) // 1回しか呼ばれない
               done <- true
          }()
     }
     for i := 0; i < 10; i++ {
          <-done
     }
}

WaitGroup型

複数のgoroutineを待つときに使います。

http
package main

import (
     "fmt"
     "sync"
     "time"
)

func main() {
     var wg sync.WaitGroup
     var urls = []string{
          "http://www.golang.org/",
          "http://www.google.com/",
          "http://www.somestupidname.com/",
     }

     // なんか重い処理
     heavyExec := func(s string) {
          time.Sleep(1 * time.Second)
          fmt.Println(s)
     }

     for _, url := range urls {
          wg.Add(1)
          go func(url string) {
               defer wg.Done()
               heavyExec(url)
          }(url)
     }
     wg.Wait()
}

結果

http://www.golang.org/
http://www.somestupidname.com/
http://www.google.com/

func (*WaitGroup) Add() 内部counterを追加(引数でインクリメントの値を設定可能)
func (*WaitGroup) Done counterをデクリメント(1のみデクリメント)
func (*WaitGroup) Wait 内部カウンターが0になるまでブロック

Cond型

先にゴルーチンを用意して、一斉に実行したい場合はCondを使います。

package main

import (
  "fmt"
  "sync"
)

func main() {
  m := new(sync.Mutex)
  c := make(chan bool)
  cond := sync.NewCond(m)

  for i := 0; i < 5; i++ {
    go func(i int) {
      m.Lock()
      defer m.Unlock()
      cond.Wait()
      fmt.Println(i)
      c <- true
    }(i)
  }
  for i := 0; i < 5; i++ {
    go func() {
      cond.Signal()
    }()
    <-c
  }
}

func (c *Cond) Signal() で1回ずつ実行。
func (c *Cond) Broadcast() で登録されているすべてを実行。

Broadcastを使った場合: http://play.golang.org/p/EC6_Gkhqo5

Pool型

非同期でくる割り込みに対応したい場合に利用します。

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    c := make(chan bool)

    pool := sync.Pool{
        New: func() interface{} {
            return "New"
        },
    }

    for i := 0; i < 5; i++ {
        go func() {
            time.Sleep(100 * time.Millisecond)
            fmt.Println(pool.Get())
            c <- true
        }()
        <-c
    }
}

この場合結果はすべて

New
New
New
New
New

になります。

途中で別なものを取得したい場合

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    c := make(chan bool)

    pool := sync.Pool{
        New: func() interface{} {
            return "New"
        },
    }

    // 175ms後と350ms後に割り込みを入れる 
    for i := 0; i < 2; i++ {
        go func(i int) {
            time.Sleep(175 * time.Duration(i+1) * time.Millisecond)
            pool.Put("Put")
        }(i)
    }

    for i := 0; i < 5; i++ {
        go func() {
            time.Sleep(100 * time.Millisecond)
            fmt.Println(pool.Get())
            c <- true
        }()
        <-c
    }
}

結果

New
Put
New
Put
New

参考

92
77
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
92
77