ホームページ バックエンド開発 Golang Go言語でのGoroutineの使い方を詳しく解説

Go言語でのGoroutineの使い方を詳しく解説

Nov 25, 2019 pm 02:34 PM
言語を移動

Go言語でのGoroutineの使い方を詳しく解説

go の goroutine は、言語レベルで同時実行性をサポートする go language の機能です。初めて Go に触れたとき、Go の goroutine に非常に満足しました。並行性の実装はばかばかしいほど簡単です。

しかし、プロジェクトのプロセス中に、Goroutine は誰でも簡単に悪用できるものであることがますますわかりました。ゴルーチンは両刃の剣です。 goroutine を使用することの罪は次のとおりです:

1. goroutine で渡すポインタは安全ではありません

fun main() {
    request := request.NewRequest() //这里的NewRequest()是传递回一个type Request的指针
    go saveRequestToRedis1(request)
    go saveReuqestToRedis2(request)
     
    select{}
 
}
ログイン後にコピー

非常に論理的なコード :

メイン ルーチンは、リクエストを saveRequestToRedis1 に渡すルーチンを開き、リクエストを Redis ノード 1 に保存できるようにします。

同時に、別のルーチンを開いてリクエストを saveReuqestToRedis2 に渡します。リクエストを保存できます。redis ノード 2 に移動します。

その後、メイン ルーチンが (プロセスを終了せずに) ループに入ります。

ここで問題が発生します。saveRequestToRedis1 と saveReuqestToRedis2 の 2 つの関数は、実際には存在しませんでした。誰かが書いたものですが、その実装については何も知りませんし、特定の内部実装を詳しく調べたくありません。しかし、関数名に従って、それを当然のこととして、リクエスト ポインタを渡しました。

実際、saveRequestToRedis1 と saveRequestToRedis2 は次のように実装されています。

func saveRequestToRedis1(request *Request){
     …
     request.ToUsers = []int{1,2,3} //这里是一个赋值操作,修改了request指向的数据结构
     …
    redis.Save(request)
    return
}
ログイン後にコピー

これの何が問題なのでしょうか? 2 つのゴルーチン saveRequestToRedis1 と saveReuqestToRedis2 は同じ共有データ構造を変更しますが、ルーチンの実行には順序がないため、request.ToUsers 設定と redis.Save() がアトミックな操作であることは保証できません。データエラーバグが発生します。

そうですね、この saveRequestToRedis 関数の実装には問題があり、go ルーチンを使用して呼び出されるということは考慮されていなかったと言えます。 saveRequestToRedis の具体的な実装は問題ありませんが、上位層での使用方法は考慮しないでください。

それは、私の goroutine の使用に問題があるためです。メイン ルーチンがルーチンを開くとき、ルーチン内のコードがメイン ルーチンのデータを変更したかどうかは確認されません。はい、メインルーチンはこの状況を考慮する必要があります。

メインのゴルーチンが go ルーチンを有効にすると、サブルーチン内のコードの各行を読み取って、共有データが変更されたかどうかを判断する必要があります。 ?実際のプロジェクト開発プロセスでは、これがどれほど開発スピードを遅らせることになるでしょうか。

Go 言語は同時開発のプレッシャーを軽減するために goroutine を使用していますが、それが逆に開発のプレッシャーを高めることになるとは考えていませんでした。

上記では多くのことを述べてきましたが、結論としては次のとおりです:

gotine ポインタの受け渡しは安全ではありません。 !

前の例が十分にわかりにくい場合は、別の例を示します:

fun (this *Request)SaveRedis() {
    redis1 := redis.NewRedisAddr("xxxxxx")
    redis2 := redis.NewRedisAddr("xxxxxx")
    go this.saveRequestToRedis(redis1)
    go this.saveRequestToRedis(redis2)
     
    select{}
}
ログイン後にコピー

このポインターが指すオブジェクトに問題があるかどうかを考える人はほとんどいないでしょう。ここでの this ポインターは次のとおりです。ルーチンに渡されます。これは非常に隠蔽されていると言うべきです。

2. Goroutine は関数の危険因子を増加させます

この点は、実際には上記の点から派生しています。上で述べたように、Go 関数にポインターを渡すのは安全ではありません。では、別の角度から考えてみましょう。呼び出したい関数が関数実装内で使用されないことをどのように保証できるでしょうか?関数本体内の特定の実装を見ない限り、それを判断する方法はありません。

たとえば、上記の典型的な例を少し変更すれば、

func main() {
    request := request.NewRequest()
    saveRequestToRedis1(request)
    saveRequestToRedis2(request)
    select{}
}
ログイン後にコピー

同時実行性を使用していないので、この問題は絶対に発生しませんよね。私は関数を追いかけましたが、唖然としました。

func saveReqeustToRedis1(request *Request) {
           …
            go func() {
          …
          request.ToUsers = []{1,2,3}
         ….
         redis.Save(request)
    }
}
ログイン後にコピー

ゴルーチンが内部で開始され、リクエスト ポインタが指すオブジェクトを変更しました。ここでエラーが発生しました。まあ、関数を呼び出すときに関数内の特定の実装を見ない限り、この問題は回避できません。

つまり、最悪の観点から見ると、すべての関数呼び出しは理論的には安全ではありません。想像してみてください。この呼び出し関数が私自身の開発チームの誰かによって書かれたのではなく、インターネット上のサードパーティのオープン ソース コードを使用していたとしたら...このバグを見つけるのにどれくらいの時間がかかるか、本当に想像できません。

3. Goroutine 悪用トラップ

この例を見てください:

func main() {
    go saveRequestToRedises(request)
}
 
func saveRequestToRedieses(request *Request) {
    for _, redis := range Redises {
        go redis.saveRequestToRedis(request)
    }
}
 
func saveRequestToRedis(request *Request) {
            ….
            go func() {
                     request.ToUsers = []{1,2,3}
                        …
                        redis.Save(request)
            }
 
}
ログイン後にコピー

すごいですね、go はどこにでもあるようです瞬きする間にどこからともなく現れること。これは go の乱用です。go はどこにでもありますが、あまり明確ではありません。どこで go を使用すればよいでしょうか?なぜ go を使うのでしょうか? goroutine は本当に効率を向上させるのでしょうか?

C 言語の同時実行は Go 言語の同時実行よりもはるかに複雑で面倒なので、使用する前によく考え、同時実行を使用するメリットとデメリットを検討します。

処理方法

これらの問題に対処するための私の方法をいくつか紹介します:

1.開始ゴルーチンを使用する場合、関数がポインタを渡す必要があるが、関数レベルが非常に深く安全性が保証できない場合は、ポインタを直接渡すのではなく、オブジェクトのクローンにポインタを渡します

fun main() {
    request := request.NewRequest()
    go saveRequestToRedis1(request.Clone())
    go saveReuqestToRedis2(request.Clone())
     
    select{}
 
}
ログイン後にコピー

Clone関数は別途記述する必要があります。構造を定義した後は、この方法に簡単に従うことができます。例えば:###

func (this *Request)Clone(){
    newRequest := NewRequst()
    newRequest.ToUsers = make([]int, len(this.ToUsers))
    copy(newRequest.ToUsers, this.ToUsers)
 
}
ログイン後にコピー

其实从效率角度考虑这样确实会产生不必要的Clone的操作,耗费一定内存和CPU。但是在我看来,首先,为了安全性,这个尝试是值得的。

其次,如果项目对效率确实有很高的要求,那么你不妨在开发阶段遵照这个原则使用clone,然后在项目优化阶段,作为一种优化手段,将不必要的Clone操作去掉。这样就能在保证安全的前提下做到最好的优化。

2、什么时候使用go的问题

有两种思维逻辑会想到使用goroutine:

1 业务逻辑需要并发

比如一个服务器,接收请求,阻塞式的方法是一个请求处理完成后,才开始第二个请求的处理。其实在设计的时候我们一定不会这么做,我们会在一开始就已经想到使用并发来处理这个场景,每个请求启动一个goroutine为它服务,这样就达到了并行的效果。这种goroutine直接按照思维的逻辑来使用goroutine

2 性能优化需要并发

一个场景是这样:需要给一批用户发送消息,正常逻辑会使用

for _, user := range users {
    sendMessage(user)
 
}
ログイン後にコピー

但是在考虑到性能问题的时候,我们就不会这样做,如果users的个数很大,比如有1000万个用户?我们就没必要将1000万个用户放在一个routine中运行处理,考虑将1000万用户分成1000份,每份开一个goroutine,一个goroutine分发1万个用户,这样在效率上会提升很多。这种是性能优化上对goroutine的需求

按照项目开发的流程角度来看。在项目开发阶段,第一种思路的代码实现会直接影响到后续的开发实现,因此在项目开发阶段应该马上实现。

但是第二种,项目中是由很多小角落是可以使用goroutine进行优化的,但是如果在开发阶段对每个优化策略都考虑到,那一定会直接打乱你的开发思路,会让你的开发周期延长,而且很容易埋下潜在的不安全代码。

因此第二种情况在开发阶段绝不应该直接使用goroutine,而该在项目优化阶段以优化的思路对项目进行重构。

推荐:golang开发栏目

以上がGo言語でのGoroutineの使い方を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

GOの浮動小数点番号操作に使用されるライブラリは何ですか? GOの浮動小数点番号操作に使用されるライブラリは何ですか? Apr 02, 2025 pm 02:06 PM

GO言語の浮動小数点数操作に使用されるライブラリは、精度を確保する方法を紹介します...

Go's Crawler Collyのキュースレッドの問題は何ですか? Go's Crawler Collyのキュースレッドの問題は何ですか? Apr 02, 2025 pm 02:09 PM

Go Crawler Collyのキュースレッドの問題は、Go言語でColly Crawler Libraryを使用する問題を調査します。 �...

Goでは、Printlnとstring()関数を備えた文字列を印刷すると、なぜ異なる効果があるのですか? Goでは、Printlnとstring()関数を備えた文字列を印刷すると、なぜ異なる効果があるのですか? Apr 02, 2025 pm 02:03 PM

Go言語での文字列印刷の違い:printlnとstring()関数を使用する効果の違いはGOにあります...

Redisストリームを使用してGO言語でメッセージキューを実装する場合、user_idタイプの変換の問題を解決する方法は? Redisストリームを使用してGO言語でメッセージキューを実装する場合、user_idタイプの変換の問題を解決する方法は? Apr 02, 2025 pm 04:54 PM

redisstreamを使用してGo言語でメッセージキューを実装する問題は、GO言語とRedisを使用することです...

Golandのカスタム構造ラベルが表示されない場合はどうすればよいですか? Golandのカスタム構造ラベルが表示されない場合はどうすればよいですか? Apr 02, 2025 pm 05:09 PM

Golandのカスタム構造ラベルが表示されない場合はどうすればよいですか?ゴーランドを使用するためにGolandを使用する場合、多くの開発者はカスタム構造タグに遭遇します...

GO言語の「VAR」と「タイプ」キーワード定義構造の違いは何ですか? GO言語の「VAR」と「タイプ」キーワード定義構造の違いは何ですか? Apr 02, 2025 pm 12:57 PM

GO言語で構造を定義する2つの方法:VARとタイプのキーワードの違い。構造を定義するとき、GO言語はしばしば2つの異なる執筆方法を見ます:最初...

GOのどのライブラリが大企業によって開発されていますか、それとも有名なオープンソースプロジェクトによって提供されていますか? GOのどのライブラリが大企業によって開発されていますか、それとも有名なオープンソースプロジェクトによって提供されていますか? Apr 02, 2025 pm 04:12 PM

大企業または有名なオープンソースプロジェクトによって開発されたGOのどのライブラリが開発されていますか? GOでプログラミングするとき、開発者はしばしばいくつかの一般的なニーズに遭遇します...

SQL.Openを使用するとき、DSNが空に渡されたときにエラーを報告しないのはなぜですか? SQL.Openを使用するとき、DSNが空に渡されたときにエラーを報告しないのはなぜですか? Apr 02, 2025 pm 12:54 PM

SQL.Openを使用する場合、DSNがエラーを報告しないのはなぜですか? GO言語では、sql.open ...

See all articles