関数または関数の本体がgolangの2つのスレッドによって呼び出されないようにロックするにはどうすればよいですか?
私の使用例は、一度に1人の発信者しか持てないシリアルインターフェイスを呼び出しているWebサーバーがあり、2つの呼び出しは、シリアル回線上で互いにノイズを生成することによって互いにキャンセルします。
最も簡単な方法は、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秒間隔で印刷されていることがわかります。
ミューテックスを使用した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
}
_
これにより、コマンドハンドラーはコマンドの発信者に値を送り返すことができます。遊び場での完全な例 ここ 。
非再入可能関数を実装するには、次の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での非再入可能関数の実装 を検討してください。