ゴルーチン+チャネルの一般的な方法のモデルでgolang

ゴルーチンスレッドとは異なり、スレッドオペレーティングシステムは、例えば、別の操作で説明されている、スレッドを達成するための異なるオペレーティングシステムが、同じではないが、オペレーティングシステムが存在ゴルーチンを知らない、ゴルーチンスケジューリング動作はGolangあります時に管理。スレッドを開始するために必要なリソースは以下のプロセスよりもですが、複数の切り替えスレッドコンテキストが、まだ(登録/プログラムカウント/スタックポインタ/ ...)多くの作業を必要とする、Golangはゴルーチンの多く、独自のスケジューラがありますがゴルーチンの切り替えは、はるかに高速になり非常に少ないリソースを消費ゴルーチンを開始しますので、データは、共有されている、Golang手順は正常ですゴルーチン数百人が存在します。

チャネル、すなわち、「パイプライン」(メッセージがより適切と呼ばれる)データを渡すために使用されるデータ構造であり、すなわち、プラグ内部のチャネルからのデータは、データから取得することができます。チャネル内に配置されるシンプルで強力な要求処理モデル、すなわちN中間結果ゴルーチン作業工程又は最終結果を形成するために、魔法の場所が、添加ゴルーチンチャネルない自体チャネル、および他のMのゴルーチンは、このデータから取るためにチャネルを作動し、次いで、さらにそれによって複雑なビジネスモデルの多様を行う、そのような方法の組み合わせによって処理されます。

モデル

いくつかのゴルーチン+チャネルによって実装作業のモデルを生産し、自分の練習の過程では、本論文では、これらのモデルのそれぞれについて説明します。

V0.1:キーワードを行きます

プラスあなたは主な機能は、直接非常に時間のかかる操作が完了するのを待つ必要がなく、操作の残りの部分を続行している、独立前の主な機能のうち、機能させることができ、キーワードに直接アクセスしてください。たとえば、私たちは前にリクエストを受信した後、時間のかかるタスクを実行するためのサービスモジュールの後に書きます。次のような:

func (m *SomeController) PorcessSomeTask() {
    var task models.Task
    if err := task.Parse(m.Ctx.Request); err != nil {
        m.Data["json"] = err 
        m.ServeJson()
        return
    }
    task.Process()
    m.ServeJson()

プロセス機能は多くの時間がかかる場合、要求ブロックがライブになります。時には、フロントエンドは、バックエンドへの後端部をリクエストを送信する必要があるとそれは迅速な対応を必要としません。ミート直接フロント時間のかかる機能の間のフロントエンドに戻って、この需要は、あなたが要求を行って、その経験を確実にするために、キーワードを追加することができます

func (m *SomeController) PorcessSomeTask() {
    var task models.Task
    if err := task.Parse(m.Ctx.Request); err != nil {
        m.Data["json"] = err 
        m.ServeJson()
        return
    }
    go task.Process()
    m.ServeJson()

しかし、このアプローチは、多くの制限があります。例えば:

  • 場合後端の唯一のフロントエンドは、即時処理結果を必要としない場合
  • 現在のアプローチは、同時実行制御ではないため、このような要求の頻度は、大きくてはなりません

V0.2:同時実行制御

プログラムは欠点がある上、同じ期間の要求のこのタイプは、言葉がたくさんある場合は、各要求は、各ゴルーチンは、他のシステムリソースを使用する必要がある場合、消費量が制御不能になり、ゴルーチンを開始するには、同時実行の制御を超えています。

このような場合には、溶液である:チャネルを有するように要求を転送し、その後、初期複数ゴルーチンこのチャネル、及び処理の内容を読み取ります。私たちはグローバルチャンネルを作成することができると仮定

var TASK_CHANNEL = make(chan models.Task)
然后,启动多个goroutine:

for i := 0; i < WORKER_NUM; i ++ {
    go func() {
        for {
            select {
            case task := <- TASK_CHANNEL:
                task.Process()
            }
        }
    } ()
}

サービス終了要求を受信すると、受信したチャンネルのタスクが使用できます

func (m *SomeController) PorcessSomeTask() {
    var task models.Task
    if err := task.Parse(m.Ctx.Request); err != nil {
        m.Data["json"] = err 
        m.ServeJson()
        return
    }
    //go task.Process()
    TASK_CHANNEL <- task
    m.ServeJson()
}

このように、同時動作の程度がWORKER_NUMによって制御することができます。

V0.3:フルケースハンドリングチャンネル

しかしながら、上記のプログラムにバグがあります、チャネルの初期化が全く設定された長さではない場合、すべてWORKER_NUMゴルーチンが要求を処理されているときに、次に来る要求があった場合、依然としてケースブロックすること、及び無しよります最適化プログラムも遅い(1つのゴルーチンの終了時にそれに対処するために待機するため)になります。このように、初期化時にチャネルの長さを増加する必要があります。

var TASK_CHANNEL = make(chan models.Task, TASK_CHANNEL_LEN)

TASK_CHANNEL_LENが十分に大きく設定され、結果として、私たちは、あなたが同時にブロックであることの恐れなしTASK_CHANNEL_LEN要求を受信するために要求することができます。しかし、これは問題にはまだ実際には:本当にTASK_CHANNEL_LENがその上に要求した同じ時間より長いがある場合はどうすれば?一方で、これはアーキテクチャの問題と見なされるべきであり、それは、モジュールの拡張およびその他の操作を介して解決することができます。一方、モジュール自体もどのように考慮しなければならない「優雅な劣化。」このような場合には、我々はモジュールは速やかに、発信者に知らせることができる期待するべきである、「私はリミット処理は、あなたがリクエストを処理することはできません達しています。」チャネル送信と受信の操作がselect文で実行した場合や障害物は、デフォルトの文が直ちに実行されます、発生します。実際には、この需要は非常に単純なGolangで実現することができます。

select {
case TASK_CHANNEL <- task:
    //do nothing
default:
    //warnning!
    return fmt.Errorf("TASK_CHANNEL is full!")
}
//...

v0.4が:伝送チャネルに返される結果を受け取った後

ハンドラは、より複雑な場合、彼らは通常にゴルーチンに表示され、我々はいくつかの中間処理結果が最終的な結果を得るために、他のゴルーチン行い、マルチチャンネルを通じて「プロセス」に送られ送信されます。

そこで、我々は唯一のチャンネルに一つの中間結果を送信する必要がありますが、この要求の処理の結果を取得することができません。溶液は:要求に含まれるチャネルのインスタンスは、ゴルーチン処理結果がこのチャネルに書き戻します完了する

type TaskResponse struct {
    //...
}

type Task struct {
    TaskParameter   SomeStruct
    ResChan         *chan TaskResponse
}

//...

task := Task {
    TaskParameter   : xxx,
    ResChan         : make(chan TaskResponse),
}

TASK_CHANNEL <- task
res := <- task.ResChan
//...

(ここでは疑問であってもよい:複雑なタスクを考慮に異なるサブタスクを取る必要があるので、それがあるゴルーチンを実行するために配置されていないのはなぜ、異なるシステムリソースを消費し、いくつかのCPUは、集中しますか?いくつかのIOを濃縮し、異なるチャネルを介して+ゴルーチンを完了するために必要であり、同時これらのサブタスクの異なる数を設定する必要があります。)

V0.5:ゴルーチンのグループの帰りを待っています

タスクグループ後、異なるゴルーチンが処理のために引き渡され、その後、各ゴルーチンプロセスの最終結果をマージし、これはより一般的なプロセスです。グループのゴルーチンを同期させるためにWaitGroupを使用する必要があります。次のように一般的なプロセスは次のとおりです。

var wg sync.WaitGroup
for i := 0; i < someLen; i ++ {
    wg.Add(1)
    go func(t Task) {
        defer wg.Done()
        //对某一段子任务进行处理
    } (tasks[i])
}

wg.Wait()
//处理剩下的工作

V0.6:タイムアウトメカニズム

でも、複雑で時間のかかるタスクを、あなたはまた、タイムアウトを設定する必要があります。一方で、これは他の要求が正常に処理できないように、タスクを終了することができなかった上、それ自体が消費することができない一方、モジュールのタイム限定サービス要件(ユーザがXXの数分以内に結果を見なければならない)とすることができます。したがって、プロセスを処理するためのタイムアウトメカニズムを大きくする必要があります。

、および前述の結合「の結果を受信した後にチャネルへの復帰」外側チャンネルを待機セレクトリターンを添加し、ここでtime.After()によってタイムアウトを決定するために:私は、一般的な解決策であるタイムアウトを設定します。

task := Task {
    TaskParameter   : xxx,
    ResChan         : make(chan TaskResponse),
}

select {
case res := <- task.ResChan:
    //...
case <- time.After(PROCESS_MAX_TIME):
    //处理超时
}

V0.7:ブロードキャストメカニズム

タイムアウトメカニズムがあるので、それはまた、他のものゴルーチン手の終わりやっているし、終了を通知する仕組みが必要です。もちろん、まだチャネルを使用して通信する必要があり、最初に考えたのは1ちゃんに構造体を送信してくださいです。ゴルーチンは、メッセージチャネル、タスクの終了を受信した場合、チャンのstruct {}型パラメータを追加して、そのようなパラメータとしてタスクを実行します。しかし、我々はまた、二つの問題を解決する必要があります。

  • タスクの実行中にどのように我々は、このメッセージを受け取ることができますか?
  • お知らせゴルーチンの方法をすべての?
    最初の質問のために、よりエレガントなアプローチである:dの関数としての別の出力チャネルを使用して、プラス選択し出力することができるが、終了信号を受信している間。

一方、実行ゴルーチンの場合の未知数の数が、呼が再び行われている間、<-struct {} {}、明らかに達成できません。チャネルが閉鎖されると、すべての文はすぐに戻りますので、ブロックされたチャンネルを受信します。この時点で、それはトリッキー用チャネル使用golangを使用します。次のようにサンプルコードは次のとおりです。

// 执行方
func doTask(done <-chan struct{}, tasks <-chan Task) (chan Result) {
    out := make(chan Result)
    go func() {
        // close 是为了让调用方的range能够正常退出
        defer close(out)
        for t := range tasks {
            select {
            case result <-f(task):
            case <-done:
                return
            }
        }
    }()

    return out
}

// 调用方
func Process(tasks <-chan Task, num int) {
    done := make(chan struct{})
    out := doTask(done, tasks)

    go func() {
        <- time.After(MAX_TIME)
        //done <-struct{}{}

        //通知所有的执行goroutine退出
        close(done)
    }()

    // 因为goroutine执行完毕,或者超时,导致out被close,range退出
    for res := range out {
        fmt.Println(res)
        //...
    }
}
公開された115元の記事 ウォン称賛67 ビュー10万+

おすすめ

転載: blog.csdn.net/meifannao789456/article/details/102499907