banner
uyoung

uyoung

twitter

Golangの並行プログラミング

golang

概念#

  • Concurrency is not parallelism: 並行性は並列性ではない
  • channel には異なる種類のタイプがあり、<-、-> の矢印が channel の方向を示す
  • channel のバッファサイズは 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 { // 受信専用の文字列channelを返します。
    c := make(chan string)
    go func() { // 関数内部からgoroutineを起動します。
        for i := 0; ; i++ {
            c <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
        }
    }()
    return c // 呼び出し元にchannelを返します。
}

channel の再利用 (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("あなたは話しすぎです。")
      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 はコンテキストを意味し、以下のコードはキャンセル関数を持つ context を構築するもので、キャンセルメソッドを実行した後、watch 関数が応答して終了します。

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("よし、監視を停止するよう通知します")
  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 の並行プログラミング

このメッセージは、このフィード(feedId:79713081934950400)が私(userId:56537166843314176)に属することを確認するために使用されます。次世代の情報ブラウザを楽しむために私と一緒に参加してください https://follow.is.

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