Go 言語での並行プログラミングの深い理解 [27] [Goroutine の使用、タイマー、選択]


ゴルーチン プール

ワーカープール(ゴルーチン池)

  • 本質的に生産者消費者モデル
  • goroutine の数が急増するのを防ぐために効果的に制御できる
  • 必要:
    • 数値の桁の合計を計算します (数値 123 など)。結果は 1+2+3=6 となります。
    • 計算用の乱数を生成する
  • コンソール出力は次のとおりです。

ヌル

package main

import (
    "fmt"
    "math/rand"
)

type Job struct {
    
    
    // id
    Id int
    // 需要计算的随机数
    RandNum int
}

type Result struct {
    
    
    // 这里必须传对象实例
    job *Job
    // 求和
    sum int
}

func main() {
    
    
    // 需要2个管道
    // 1.job管道
    jobChan := make(chan *Job, 128)
    // 2.结果管道
    resultChan := make(chan *Result, 128)
    // 3.创建工作池
    createPool(64, jobChan, resultChan)
    // 4.开个打印的协程
    go func(resultChan chan *Result) {
    
    
        // 遍历结果管道打印
        for result := range resultChan {
    
    
            fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id,
                result.job.RandNum, result.sum)
        }
    }(resultChan)
    var id int
    // 循环创建job,输入到管道
    for {
    
    
        id++
        // 生成随机数
        r_num := rand.Int()
        job := &Job{
    
    
            Id:      id,
            RandNum: r_num,
        }
        jobChan <- job
    }
}

// 创建工作池
// 参数1:开几个协程
func createPool(num int, jobChan chan *Job, resultChan chan *Result) {
    
    
    // 根据开协程个数,去跑运行
    for i := 0; i < num; i++ {
    
    
        go func(jobChan chan *Job, resultChan chan *Result) {
    
    
            // 执行运算
            // 遍历job管道所有数据,进行相加
            for job := range jobChan {
    
    
                // 随机数接过来
                r_num := job.RandNum
                // 随机数每一位相加
                // 定义返回值
                var sum int
                for r_num != 0 {
    
    
                    tmp := r_num % 10
                    sum += tmp
                    r_num /= 10
                }
                // 想要的结果是Result
                r := &Result{
    
    
                    job: job,
                    sum: sum,
                }
                //运算结果扔到管道
                resultChan <- r
            }
        }(jobChan, resultChan)
    }
}

タイマー

タイマー

  • タイマー: 時間が経過すると、実行は 1 回だけ実行されます

  • タイプタイマー

    type Timer struct {
        C <-chan Time
        // 内含隐藏或非导出字段
    }
    

    Timer タイプは単一の時間イベントを表します。タイマーが AfterFunc 関数によって作成されていない限り、タイマーの期限が切れると、現在の時刻が C に送信されます。


関数NewTimer

func NewTimer(d Duration) *Timer

NewTimer は、少なくとも一定期間が経過すると期限切れになるタイマーを作成し、現在の時刻を独自の C フィールドに送信します。


関数AfterFunc

func AfterFunc(d Duration, f func()) *Timer

AfterFunc は別の goroutine を開始して期間 d が経過するのを待ってから、f を呼び出します。これは Timer を返します。待機と f の呼び出しは、Stop メソッドを呼び出すことでキャンセルできます。

func (*タイマー)リセット

func (t *Timer) Reset(d Duration) bool

リセットにより t はタイミングを再開し、(このメソッドが戻った後) 期間 d が期限切れになるまで待機します。呼び出し時に t が保留中の場合は true を返し、t の有効期限が切れているか停止している場合は false を返します。


func (*タイマー)停止

func (t *Timer) Stop() bool

Stop はタイマーの実行を停止します。t が停止している場合は true を返し、t が停止しているか期限切れである場合は false を返します。このチャネルからの読み取りが誤って成功することを避けるために、Stop はチャネル tC を閉じません。

package main

import (
    "fmt"
    "time"
)

func main() {
    
    
    // 1.timer基本使用
    //timer1 := time.NewTimer(2 * time.Second)
    //t1 := time.Now()
    //fmt.Printf("t1:%v\n", t1)
    //t2 := <-timer1.C
    //fmt.Printf("t2:%v\n", t2)

    // 2.验证timer只能响应1次
    //timer2 := time.NewTimer(time.Second)
    //for {
    
    
    // <-timer2.C
    // fmt.Println("时间到")
    //}

    // 3.timer实现延时的功能
    //(1)
    //time.Sleep(time.Second)
    //(2)
    //timer3 := time.NewTimer(2 * time.Second)
    //<-timer3.C
    //fmt.Println("2秒到")
    //(3)
    //<-time.After(2*time.Second)
    //fmt.Println("2秒到")

    // 4.停止定时器
    //timer4 := time.NewTimer(2 * time.Second)
    //go func() {
    
    
    // <-timer4.C
    // fmt.Println("定时器执行了")
    //}()
    //b := timer4.Stop()
    //if b {
    
    
    // fmt.Println("timer4已经关闭")
    //}

    // 5.重置定时器
    timer5 := time.NewTimer(3 * time.Second)
    timer5.Reset(1 * time.Second)
    fmt.Println(time.Now())
    fmt.Println(<-timer5.C)

    for {
    
    
    }
}
  • ティッカー: 時間切れです。複数回実行してください

  • タイプティッカー

    type Ticker struct {
        C <-chan Time // 周期性传递时间信息的通道
        // 内含隐藏或非导出字段
    }
    

    ティッカーはチャネルを保持し、定期的に「ティック」をチャネルに渡します。


関数NewTicker

func NewTicker(d Duration) *Ticker

NewTicker は新しいティッカーを返します。ティッカーにはチャネル フィールドが含まれており、期間 d ごとに現在の時刻をチャネルに送信します。時間間隔を調整したり、遅い受信者に対応するためにティック メッセージを破棄したりします。d<=0 の場合はパニックになります。ティッカーを閉じると、関連リソースが解放されることがあります。

func (*ティッカー)停止

func (t *Ticker) Stop()

Stop はティッカーを閉じます。閉じた後は、ティック メッセージは送信されなくなります。このチャネルからの読み取りが誤って成功することを避けるために、Stop はチャネル tC を閉じません。


機能睡眠

func Sleep(d Duration)

Sleep は、現在の goroutine を少なくとも d の期間ブロックします。d<=0 の場合、スリープはすぐに戻ります。


関数

func After(d Duration) <-chan Time

After は、別のスレッドが期間 d を経過した後、現在の時刻を戻り値に送信します。NewTimer(d).C と同等。


ファンクティック

func Tick(d Duration) <-chan Time

Tick は NewTicker のパッケージであり、Ticker のチャネルへのアクセスのみを提供します。ティッカーを閉じる必要がない場合に便利な機能です。

package main

import (
    "fmt"
    "time"
)

func main() {
    
    
    // 1.获取ticker对象
    ticker := time.NewTicker(1 * time.Second)
    i := 0
    // 子协程
    go func() {
    
    
        for {
    
    
            //<-ticker.C
            i++
            fmt.Println(<-ticker.C)
            if i == 5 {
    
    
                //停止
                ticker.Stop()
            }
        }
    }()
    for {
    
    
    }
}

選択する

多重化の選択

一部のシナリオでは、複数のチャネルから同時にデータを受信する必要があります。チャネルがデータを受信して​​いるときに、受信するデータがない場合、チャネルはブロックされます。次のコードを記述して、トラバーサルを使用して目的を達成できます。

for{
    
    
    // 尝试从ch1接收值
    data, ok := <-ch1
    // 尝试从ch2接收值
    data, ok := <-ch2
    …
}

この方法では、複数のチャネルから値を受信するという要件を満たすことができますが、動作パフォーマンスは大幅に低下します。このシナリオに対処するために、Go には複数のチャネルの操作に同時に応答できる select キーワードが組み込まれています。

select の使用法は switch ステートメントと似ており、一連の case 分岐とdefault 分岐があります。それぞれのケースは、チャネルの通信 (受信または送信) プロセスに対応します。select は、case の通信操作が完了するまで待機し、case ブランチに対応するステートメントを実行します。具体的な形式は次のとおりです。

    select {
    case <-chan1:
       // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
       // 如果成功向chan2写入数据,则进行该case处理语句
    default:
       // 如果上面都没有成功,则进入default处理流程
    }
  • select は、チャネルの 1 つが準備できるまで、1 つ以上のチャネルを同時に監視できます。
package main

import (
   "fmt"
   "time"
)

func test1(ch chan string) {
    
    
   time.Sleep(time.Second * 5)
   ch <- "test1"
}
func test2(ch chan string) {
    
    
   time.Sleep(time.Second * 2)
   ch <- "test2"
}

func main() {
    
    
   // 2个管道
   output1 := make(chan string)
   output2 := make(chan string)
   // 跑2个子协程,写数据
   go test1(output1)
   go test2(output2)
   // 用select监控
   select {
    
    
   case s1 := <-output1:
      fmt.Println("s1=", s1)
   case s2 := <-output2:
      fmt.Println("s2=", s2)
   }
}
  • 複数のチャネルが同時に準備できている場合、ランダムに 1 つを選択して実行します
package main

import (
   "fmt"
)

func main() {
    
    
   // 创建2个管道
   int_chan := make(chan int, 1)
   string_chan := make(chan string, 1)
   go func() {
    
    
      //time.Sleep(2 * time.Second)
      int_chan <- 1
   }()
   go func() {
    
    
      string_chan <- "hello"
   }()
   select {
    
    
   case value := <-int_chan:
      fmt.Println("int:", value)
   case value := <-string_chan:
      fmt.Println("string:", value)
   }
   fmt.Println("main结束")
}
  • パイプラインがいっぱいかどうかを判断するために使用できます
package main

import (
   "fmt"
   "time"
)

// 判断管道有没有存满
func main() {
    
    
   // 创建管道
   output1 := make(chan string, 10)
   // 子协程写数据
   go write(output1)
   // 取数据
   for s := range output1 {
    
    
      fmt.Println("res:", s)
      time.Sleep(time.Second)
   }
}

func write(ch chan string) {
    
    
   for {
    
    
      select {
    
    
      // 写数据
      case ch <- "hello":
         fmt.Println("write hello")
      default:
         fmt.Println("channel full")
      }
      time.Sleep(time.Millisecond * 500)
   }
}

おすすめ

転載: blog.csdn.net/m0_52896752/article/details/129797184