いくつかのGo同時制御方式を知っていますか?
14975 ワード
文書ディレクトリ
引用する
Golangではgoキーワードでgoroutineを開くことができるので、Goでコンカレントコードを簡単に書くことができます.しかし、これらの同時実行groutinesをどのように効果的に制御しますか?
同時制御といえば、多くの人が最初に考えたのはロックかもしれません.Golangでは、相互反発ロックsyncを含むロックに関するメカニズムも提供する.Mutexと読み書きロックsync.RWMutex.ロックのほか、原子操作sync/atomicなどもあります.しかし,これらのメカニズムが注目するポイントはgoroutinesの同時データセキュリティである.本稿ではgoroutineの併発を制御として議論する.
goroutineが制御として発行されるには、WaitGroup、channel、Contextの3つの一般的な方法があります.
WaitGroup
WaitGroupはsyncパッケージの下にあり、その使用方法は以下の通りです.func main() {
var wg sync.WaitGroup
wg.Add(2) // 2
go func() {
wg.Done() // 1
fmt.Println("goroutine 1 !")
}()
go func() {
wg.Done() // 1
fmt.Println("goroutine 2 !")
}()
wg.Wait() // 2
fmt.Println(" goroutine !")
}
:
//goroutine 2 !
//goroutine 1 !
// goroutine !
WaitGroupのこのような同時制御方式は特に、あるタスクには複数のgoroutine協同作業が必要であり、各goroutineはそのタスクの一部しかできず、すべてのgoroutineが完了してこそ、タスクが完了する.そのため、WaitGroupは名前の意味と同じように、待つ方法です.
しかし、実際のビジネスでは、ある要件を満たす場合、goroutineの終了をアクティブに通知するシーンがあります.例えば、バックグラウンドモニタリングgoroutineを開きます.モニタリングが必要でない場合は、このモニタリングgoroutineが終了することを通知する必要があります.そうしないと、空転し続け、漏れを起こします.
Channel
上記のシーンではWaitGroupは何もできません.それが考えられる最も簡単な方法は、グローバル変数を定義し、他の場所でこの変数を修正して通知することで、バックグラウンドgoroutineがこの変数をチェックし続け、変数が変化したことを発見したら、自分で閉じることですが、この方法は少し不器用です.この場合、channel+selectが役に立ちます.func main() {
exit := make(chan bool)
go func() {
for {
select {
case exit:
fmt.Println(" ")
return
default:
fmt.Println(" ")
time.Sleep(2 * time.Second)
}
}
}()
time.Sleep(5 * time.Second)
fmt.Println(" ")
exit true
// main goroutine
time.Sleep(5 * time.Second)
}
:
//
//
//
//
//
このchannel+selectの組み合わせは、goroutineの終了を優雅に通知する方法です.
しかし、この案にも限界がある.考えてみて、複数のgoroutineが終了を制御する必要がある場合はどうすればいいですか?もしこれらのgoroutineが他のgoroutineを派生させたら?もちろん,この問題を解決するために多くのchannelを定義することができるが,goroutineの関係チェーンはこのようなシーンの複雑さをもたらす.
Context
上記のシーンは、CSスキーマモデルでよく使用されます.Goでは、クライアントごとに個別のgoroutine(A)を開いて一連のrequestを処理することが多く、単一のAでも他のサービス(別のgoroutine Bを起動)を要求することが多く、Bは別のgoroutine Cを要求し、Cはデータbseなどのserverにrequestを送信することもある.クライアントが接続を切断すると、それに関連するA、B、Cは直ちに終了する必要があり、システムはA、B、Cが占有するリソースを回収することができると想定される.脱退Aは簡単ですが、どのようにしてB、Cにも脱退を通知しますか?
この時、Contextが登場しました.func A(ctx context.Context, name string) {
go B(ctx ,name) //A B
for {
select {
case ctx.Done():
fmt.Println(name, "A ")
return
default:
fmt.Println(name, "A do something")
time.Sleep(2 * time.Second)
}
}
}
func B(ctx context.Context, name string) {
for {
select {
case ctx.Done():
fmt.Println(name, "B ")
return
default:
fmt.Println(name, "B do something")
time.Sleep(2 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go A(ctx, "【 1】") // client 1
time.Sleep(3 * time.Second)
fmt.Println("client , client A,B ")
cancel() // client , ,ctx.Done()
time.Sleep(3 * time.Second)
}
:
//【 1】 A do something
//【 1】 B do something
//【 1】 A do something
//【 1】 B do something
//client , client A,B
//【 1】 B
//【 1】 A
例では、クライアントからの接続要求をシミュレートし、Goroutine Aをオンにして処理し、AはB処理を同時にオンにし、AとBはContextを使用して追跡し、cancel関数を使用してキャンセルを通知すると、この2つのgoroutineは終了します.
これがContextの制御能力であり、コントローラのようにスイッチを押すと、このContextまたは派生したサブContextに基づいて通知が届く.この場合、クリーンアップ操作を行い、最終的にgoroutineを解放することができ、goroutineの起動後に制御できない問題を優雅に解決することができる.
Contextの詳細な使い方については、本論文の範囲外です.Contextパッケージについての説明文が続きますので、ご注目ください.
まとめ
本論文では,3種類のGolangにおける同時挙動制御モードを列挙した.パターンの間には善し悪しの区別はなく、異なるシーンで適切な案を使うことにある.実際のプロジェクトでは、多くの方法で混合して使用されます.
WaitGroupはsyncパッケージの下にあり、その使用方法は以下の通りです.
func main() {
var wg sync.WaitGroup
wg.Add(2) // 2
go func() {
wg.Done() // 1
fmt.Println("goroutine 1 !")
}()
go func() {
wg.Done() // 1
fmt.Println("goroutine 2 !")
}()
wg.Wait() // 2
fmt.Println(" goroutine !")
}
:
//goroutine 2 !
//goroutine 1 !
// goroutine !
WaitGroupのこのような同時制御方式は特に、あるタスクには複数のgoroutine協同作業が必要であり、各goroutineはそのタスクの一部しかできず、すべてのgoroutineが完了してこそ、タスクが完了する.そのため、WaitGroupは名前の意味と同じように、待つ方法です.
しかし、実際のビジネスでは、ある要件を満たす場合、goroutineの終了をアクティブに通知するシーンがあります.例えば、バックグラウンドモニタリングgoroutineを開きます.モニタリングが必要でない場合は、このモニタリングgoroutineが終了することを通知する必要があります.そうしないと、空転し続け、漏れを起こします.
Channel
上記のシーンではWaitGroupは何もできません.それが考えられる最も簡単な方法は、グローバル変数を定義し、他の場所でこの変数を修正して通知することで、バックグラウンドgoroutineがこの変数をチェックし続け、変数が変化したことを発見したら、自分で閉じることですが、この方法は少し不器用です.この場合、channel+selectが役に立ちます.func main() {
exit := make(chan bool)
go func() {
for {
select {
case exit:
fmt.Println(" ")
return
default:
fmt.Println(" ")
time.Sleep(2 * time.Second)
}
}
}()
time.Sleep(5 * time.Second)
fmt.Println(" ")
exit true
// main goroutine
time.Sleep(5 * time.Second)
}
:
//
//
//
//
//
このchannel+selectの組み合わせは、goroutineの終了を優雅に通知する方法です.
しかし、この案にも限界がある.考えてみて、複数のgoroutineが終了を制御する必要がある場合はどうすればいいですか?もしこれらのgoroutineが他のgoroutineを派生させたら?もちろん,この問題を解決するために多くのchannelを定義することができるが,goroutineの関係チェーンはこのようなシーンの複雑さをもたらす.
Context
上記のシーンは、CSスキーマモデルでよく使用されます.Goでは、クライアントごとに個別のgoroutine(A)を開いて一連のrequestを処理することが多く、単一のAでも他のサービス(別のgoroutine Bを起動)を要求することが多く、Bは別のgoroutine Cを要求し、Cはデータbseなどのserverにrequestを送信することもある.クライアントが接続を切断すると、それに関連するA、B、Cは直ちに終了する必要があり、システムはA、B、Cが占有するリソースを回収することができると想定される.脱退Aは簡単ですが、どのようにしてB、Cにも脱退を通知しますか?
この時、Contextが登場しました.func A(ctx context.Context, name string) {
go B(ctx ,name) //A B
for {
select {
case ctx.Done():
fmt.Println(name, "A ")
return
default:
fmt.Println(name, "A do something")
time.Sleep(2 * time.Second)
}
}
}
func B(ctx context.Context, name string) {
for {
select {
case ctx.Done():
fmt.Println(name, "B ")
return
default:
fmt.Println(name, "B do something")
time.Sleep(2 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go A(ctx, "【 1】") // client 1
time.Sleep(3 * time.Second)
fmt.Println("client , client A,B ")
cancel() // client , ,ctx.Done()
time.Sleep(3 * time.Second)
}
:
//【 1】 A do something
//【 1】 B do something
//【 1】 A do something
//【 1】 B do something
//client , client A,B
//【 1】 B
//【 1】 A
例では、クライアントからの接続要求をシミュレートし、Goroutine Aをオンにして処理し、AはB処理を同時にオンにし、AとBはContextを使用して追跡し、cancel関数を使用してキャンセルを通知すると、この2つのgoroutineは終了します.
これがContextの制御能力であり、コントローラのようにスイッチを押すと、このContextまたは派生したサブContextに基づいて通知が届く.この場合、クリーンアップ操作を行い、最終的にgoroutineを解放することができ、goroutineの起動後に制御できない問題を優雅に解決することができる.
Contextの詳細な使い方については、本論文の範囲外です.Contextパッケージについての説明文が続きますので、ご注目ください.
まとめ
本論文では,3種類のGolangにおける同時挙動制御モードを列挙した.パターンの間には善し悪しの区別はなく、異なるシーンで適切な案を使うことにある.実際のプロジェクトでは、多くの方法で混合して使用されます.
func main() {
exit := make(chan bool)
go func() {
for {
select {
case exit:
fmt.Println(" ")
return
default:
fmt.Println(" ")
time.Sleep(2 * time.Second)
}
}
}()
time.Sleep(5 * time.Second)
fmt.Println(" ")
exit true
// main goroutine
time.Sleep(5 * time.Second)
}
:
//
//
//
//
//
上記のシーンは、CSスキーマモデルでよく使用されます.Goでは、クライアントごとに個別のgoroutine(A)を開いて一連のrequestを処理することが多く、単一のAでも他のサービス(別のgoroutine Bを起動)を要求することが多く、Bは別のgoroutine Cを要求し、Cはデータbseなどのserverにrequestを送信することもある.クライアントが接続を切断すると、それに関連するA、B、Cは直ちに終了する必要があり、システムはA、B、Cが占有するリソースを回収することができると想定される.脱退Aは簡単ですが、どのようにしてB、Cにも脱退を通知しますか?
この時、Contextが登場しました.
func A(ctx context.Context, name string) {
go B(ctx ,name) //A B
for {
select {
case ctx.Done():
fmt.Println(name, "A ")
return
default:
fmt.Println(name, "A do something")
time.Sleep(2 * time.Second)
}
}
}
func B(ctx context.Context, name string) {
for {
select {
case ctx.Done():
fmt.Println(name, "B ")
return
default:
fmt.Println(name, "B do something")
time.Sleep(2 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go A(ctx, "【 1】") // client 1
time.Sleep(3 * time.Second)
fmt.Println("client , client A,B ")
cancel() // client , ,ctx.Done()
time.Sleep(3 * time.Second)
}
:
//【 1】 A do something
//【 1】 B do something
//【 1】 A do something
//【 1】 B do something
//client , client A,B
//【 1】 B
//【 1】 A
例では、クライアントからの接続要求をシミュレートし、Goroutine Aをオンにして処理し、AはB処理を同時にオンにし、AとBはContextを使用して追跡し、cancel関数を使用してキャンセルを通知すると、この2つのgoroutineは終了します.
これがContextの制御能力であり、コントローラのようにスイッチを押すと、このContextまたは派生したサブContextに基づいて通知が届く.この場合、クリーンアップ操作を行い、最終的にgoroutineを解放することができ、goroutineの起動後に制御できない問題を優雅に解決することができる.
Contextの詳細な使い方については、本論文の範囲外です.Contextパッケージについての説明文が続きますので、ご注目ください.