Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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
    }
}

http://play.golang.org/p/Ehu8JgNFah

結果は

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
    }
}

http://play.golang.org/p/aYBxt8_8yO

結果

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
  }
}

http://play.golang.org/p/GO_gqR_YIm

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
    }
}

http://play.golang.org/p/xgkZs_GOug

この場合結果はすべて

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
    }
}

http://play.golang.org/p/QbGwQtn1T6

結果

New
Put
New
Put
New

参考

taizo
ruby,javascript,go とかウェブサービスに関わる小さな話を書いていきます。 最近はGoばかりやってます。基本はメモ。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした