banner
uyoung

uyoung

twitter

ゴーランド並行プログラミング

golang

概念#

  • Concurrency is not parallelism: 並行は並列とは異なる
  • channel には異なる種類の型があり、<-、-> の矢印が channel の方向を示す
  • channel のバッファサイズは、同期チャネルか非同期チャネルかを示す
  • CAS: compare-and-swap(比較と交換):並行アルゴリズムの設計に CAS 技術を使用し、マルチスレッドの安全性を向上させる

CAS#

CAS の原理:CAS には 3 つのオペランドがあります:メモリの値 V、古い予想値 A、変更する値 B。予想値 A とメモリの値 V が等しい場合にのみ、メモリの値を B に変更して true を返し、それ以外の場合は何もせずに false を返します。

  • CAS 操作は、オペレーティングシステムが対応するインターフェースを提供し、ハードウェアレベルでサポートされています。
if *addr == old {
  *addr = new
  return true
}
return false
  • CAS による golang のロックの実装
package main
  import (
    "sync/atomic"
  )
  type Mutex struct {
    state int32
  }
  func (m *Mutex) Lock() {
    for(!atomic.CompareAndSwapInt32(&m.state, 0, 1)) {
        return
    }
  }
  func (m *Mutex) Unlock() {
    atomic.CompareAndSwapInt32(&m.state, 1, 0)
  }
  • CAS を使用した sync.Map の実装:goroutine のスレッドセーフ

初級#

ジェネレータ (generator)#

channel は golang で基本的な変数型(int、string など)と同じく、最上位の型です。ジェネレータは、関数が channel を返し、関数内で channel に要素を追加するものです。

ジェネレータパターンは非常に一般的であり、最も単純なプロデューサー・コンシューマーパターンやパブリッシャー・サブスクライバーパターンを実装するために使用できます。

func boring(msg string) <-chan string { // 受信専用の文字列型チャネルを返す
    c := make(chan string)
    go func() { // ゴルーチンを関数内から起動する
        for i := 0; ; i++ {
            c <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
        }
    }()
    return c // チャネルを呼び出し元に返す
}

チャネルの再利用(select)#

func main() {
  c := boring("Joe")
  timeout := time.After(5 * time.Second)
  timer := time.NewTimer(3 * time.Second)
  for {
    select {
    case s := <-c:
      fmt.Println(s)
    case <-timer.C:
      // タイマーイベント
      timer.Reset(3 * time.Second)
    case <-timeout: // タイムアウトイベントの監視
      fmt.Println("You talk too much.")
      return
    case <-quit: // 終了イベントの監視
      return
    }
  }
}

並行制御(WaitGroup->chan->context)#

WaitGroup#

WaitGroup は、並行制御の基礎となるクラスです。

func main() {
  var wg sync.WaitGroup

  wg.Add(2)
  go func() {
      time.Sleep(2*time.Second)
      fmt.Println("1号完成")
      wg.Done()
  }()
  go func() {
      time.Sleep(2*time.Second)
      fmt.Println("2号完成")
      wg.Done()
  }()
  wg.Wait()
  fmt.Println("よし、みんな終わった、仕事終了")
}

Context#

context は、上位の親 context から新しい context を派生させるために使用される 4 つの関数があります。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
  • WithCancel:ctx とキャンセル関数を返します。
  • WithDeadline:ctx とキャンセル関数を返します。時間が deadline を超えると自動的にキャンセルされます。
  • WithTimeout:WithDeadline と同様ですが、タイムアウト時間です。
  • WithValue:メタデータを渡し、ctx.Value (key) の形式でデータを取得します。

context は、通常、リクエストの各 goroutine を処理するために使用されます。

func main() {
  ctx, cancel := context.WithCancel(context.Background())
  go watch(ctx,"【監視1】")
  go watch(ctx,"【監視2】")
  go watch(ctx,"【監視3】")

  time.Sleep(10 * time.Second)
  fmt.Println("OK、監視を停止します")
  cancel()
  // 監視が停止したかどうかを確認するため、監視の出力がない場合は停止したとみなします
  time.Sleep(5 * time.Second)
}

func watch(ctx context.Context, name string) {
  for {
    select {
    case <-ctx.Done():
      fmt.Println(name,"監視終了、停止しました...")
      return
    default:
      fmt.Println(name,"goroutine監視中...")
      time.Sleep(2 * time.Second)
    }
  }
}

ワーカースケジューリング#

基本的なワーカーキュースケジューリングの方法です。

idleWorker<-workerQueue

func(){
  idleWorker.DoTask()
  defer workerQueue<-idleWorker
}

参考#

golang 並行プログラミング

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。