web-dev-qa-db-ja.com

golangで関数スレッドを安全にする方法

関数または関数の本体がgolangの2つのスレッドによって呼び出されないようにロックするにはどうすればよいですか?

私の使用例は、一度に1人の発信者しか持てないシリアルインターフェイスを呼び出しているWebサーバーがあり、2つの呼び出しは、シリアル回線上で互いにノイズを生成することによって互いにキャンセルします。

7
Pylinux

最も簡単な方法は、sync.Mutexを使用することです。

package main

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

var lock sync.Mutex

func main() {
    go importantFunction("first")
    go importantFunction("second")
    time.Sleep(3 * time.Second)
}


func importantFunction(name string) {
    lock.Lock()
    defer lock.Unlock()
    fmt.Println(name)
    time.Sleep(1 * time.Second)
}

ここでは、「first」と「second」がルーチンであるにもかかわらず、1秒間隔で印刷されていることがわかります。

遊び場に行く: https://play.golang.org/p/mXKl42zRW8

8
Pylinux

ミューテックスを使用したPylinuxのソリューションは、彼が言うように、おそらくあなたの場合は最も単純です。ただし、代わりにもう1つ追加します。それはあなたの場合に当てはまるかもしれないし、当てはまらないかもしれません。

ミューテックスを使用する代わりに、単一のゴルーチンでシリアルインターフェイスのすべての操作を実行し、チャネルを使用して実行する必要のある作業をシリアル化することができます。

_package main

import (
    "fmt"
    "sync"
)

// handleCommands will handle commands in a serialized fashion
func handleCommands(opChan <-chan string) {
    for op := range opChan {
        fmt.Printf("command: %s\n", op)
    }
}

// produceCommands will generate multiple commands concurrently
func produceCommands(opChan chan<- string) {
    var wg sync.WaitGroup
    wg.Add(2)
    go func() { opChan <- "cmd1"; wg.Done() }()
    go func() { opChan <- "cmd2"; wg.Done() }()
    wg.Wait()
    close(opChan)
}

func main() {
    var opChan = make(chan string)
    go produceCommands(opChan)
    handleCommands(opChan)
}
_

ミューテックスと比較した場合のこれの利点は、待機キューをより細かく制御できることです。ミューテックスを使用すると、キューはLock()に暗黙的に存在し、無制限になります。一方、チャネルを使用すると、待機する発信者の最大数を制限し、同期されたコールサイトが過負荷になった場合に適切に対応できます。 len(opChan)を使用して、キューにあるゴルーチンの数を確認するなどの操作を行うこともできます。

編集して追加:

上記の例の制限(コメントに記載されている)は、計算から元の送信者に結果を返すことを処理しないことです。これを行う1つの方法は、チャネルを使用するアプローチを維持しながら、各コマンドに結果チャネルを導入することです。したがって、コマンドチャネルを介して文字列を送信する代わりに、次の形式の構造体を送信できます。

_type operation struct {
    command string
    result  chan string
}
_

コマンドは、次のようにコマンドチャネルにエンキューされます。

_func enqueueCommand(opChan chan<- operation, cmd string) <-chan string {
    var result = make(chan string)
    opChan <- operation{command: cmd, result: result}
    return result
}
_

これにより、コマンドハンドラーはコマンドの発信者に値を送り返すことができます。遊び場での完全な例 ここ

3
Josef Grahn

非再入可能関数を実装するには、次の2つのアプローチがあります。

  • ブロッキング:最初の呼び出し元が関数を実行し、後続の呼び出し元がブロックして関数が終了するまで待機してから、関数を実行します
  • 降伏:最初の呼び出し元が関数を実行し、後続の呼び出し元は関数の実行中に中止します

2つのアプローチには異なるメリットがあります。

  • 非再入可能関数のブロックは、試行された回数だけ実行されることが保証されています。ただし、実行時間が長い場合はバックログになり、その後に実行がバーストする可能性があります。
  • 非再入可能関数を生成すると、輻輳やバーストが発生しないことが保証され、最大実行率が保証されます。

@Pylinuxの回答で説明されているように、非再入可能関数のブロックは、mutexを介して最も簡単に実装されます。降伏する非リエントラント関数は、次のように、アトミックコンペアアンドスワップを介して実装できます。

_import (
    "sync/atomic"
    "time"
)

func main() {
    tick := time.Tick(time.Second)
    var reentranceFlag int64
    go func() {
        for range tick {
            go CheckSomeStatus()
            go func() {
                if atomic.CompareAndSwapInt64(&reentranceFlag, 0, 1) {
                    defer atomic.StoreInt64(&reentranceFlag, 0)
                } else {
                    return
                }
                CheckAnotherStatus()
            }()
        }
    }()
}
_

上記では、CheckAnotherStatus()は再入力から保護されているため、最初の呼び出し元はreentranceFlagを_1_に設定し、後続の呼び出し元は同じことを行わずに終了します。

より詳細な議論については、私のブログ投稿 Golangでの非再入可能関数の実装 を検討してください。

2
Shlomi Noach