同時プログラミング - タイマー、ティッカー、待機グループ、その他の一般的なモデル

Go 同時プログラミング - タイマー、ティッカー、WaitGroup の使用法

1 タイマー (1 回実行)

1.1 コンセプト

一定期間後にタスクを実行する必要がある場合は、time.Timer を使用できます。タイマーは一定期間後に時間値をチャネルに送信します。これはタスクの実行をトリガーするために使用できます。具体的には、一定期間後にタスクを実行する必要がある場合、Timer を作成し、<-timer.C ステートメントを使用して Timer.C チャネルから時間値を読み取ります。タスクを実行します。

1.2 使用法

package main

import (
	"fmt"
	"time"
)

func main() {
    
    
	timer := time.NewTimer(2 * time.Second)
	defer timer.Stop()
	<-timer.C
	fmt.Println("timer expired")
}

結果:

timer expired

2 ティッカー (定期的に複数回実行)

2.1 コンセプト

タスクを定期的に実行する必要がある場合は、time.Ticker を使用できます。Ticker は時間値をチャネルに定期的に送信し、これを使用して定期的なタスクの実行をトリガーできます。具体的には、特定のタスクを定期的に実行する必要がある場合、Ticker を作成し、for range ループを使用して Ticker.C チャネルから時間値を読み取ります。時間値が読み取られるたびに、定期的なタスクが実行されます。

2.2 使用法

package main

import (
	"fmt"
	"time"
)

func main() {
    
    
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()
	for {
    
    
		select {
    
    
		case <-ticker.C:
			fmt.Println("tick")
		}
	}
}

結果:

tick
tick
tick
tick
...

3 WaitGroup (すべてのゴルーチンが完了するまで待機します)

3.1 コンセプト

goroutine のグループが完了するまで待つ必要がある場合は、sync.WaitGroup を使用できます。WaitGroup は、ゴルーチンのグループが作業を完了するのを待つために使用されます。具体的には、ゴルーチンが他のゴルーチンのグループが完了するのを待つ必要がある場合、WaitGroup の Add メソッドを呼び出してカウンター値をインクリメントし、ゴルーチンのグループを開始できます。各ゴルーチンが完了したら、Done メソッドを呼び出してカウンターをデクリメントする必要があります。最後に、このゴルーチンのセットが完了するのを待っている間に、カウンターが 0 に達するまでブロックする Wait メソッドを呼び出す必要があります。

3.2 使用法

基本的な使い方:

package main

import (
	"fmt"
	"sync"
)

func main() {
    
    
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
    
    
		wg.Add(1)
		go func(i int) {
    
    
			defer wg.Done()
			fmt.Printf("goroutine %d\n", i)
		}(i)
	}
	wg.Wait()
	fmt.Println("all goroutines done")
}

他4名

4.1 Golang はプロデューサー/コンシューマー モデルを実装します

①簡易版

このバージョンには、プロデューサとコンシューマが 1 つだけ含まれており、ゴルーチンとチャネルを使用してメッセージ パッシングを実装します。

  • Done 変数は、すべてのサブコルーチンの終了を待機するメイン スレッドでブロックするために使用されます。コードの実行中に、サブコルーチンがタスクを完了したことを示す信号が Done 変数 (done <- true) に送信されます。すべてのサブコルーチンがタスクを完了したら、<-done を実行するとメインスレッドがブロックされ、すべてのサブコルーチンがタスクを完了するまで待機します。生成と消費では、done は単純な通信タスクにのみ使用され、メインスレッドをブロックしません。
package main

import (
	"fmt"
	_ "time"
)

func produce(ch chan<- int, done chan<- bool) {
    
    
	for i := 1; i < 3; i++ {
    
    
		ch <- i
		fmt.Printf("生产者生产 %d\n", i)
	}
	done <- true
}

func consume(ch <-chan int) {
    
    
	for i := range ch {
    
    
		fmt.Printf("消费者消费 %d\n", i)
	}
}

func main() {
    
    
	ch := make(chan int)
	done := make(chan bool)

	go produce(ch, done)
	go consume(ch)

	<-done
}

②上級編

このバージョンには、バッファリングされたチャネルと待機グループを使用して実装された複数のプロデューサーとコンシューマーが含まれています。

type Task struct{
    
    }  //自己实际需要的数据结构
producer()  //实际生产数据逻辑
consumer()  //实际处理逻辑

main()中的consumerNum(消费者个数)channelLen(通道长度)也可根据实际需要修改

コード:

package main

import (
	"fmt"
	"sync"
)

type Task struct {
    
    
	Data string
}

var wg sync.WaitGroup

//生产逻辑
func producer(tasks chan Task) {
    
    
	t := Task{
    
    }

	for i := 62; i < 72; i++ {
    
    
		t.Data = string(i)
		tasks <- t
	}
}

func producerDispatch(tasks chan Task) {
    
    
	defer close(tasks)

	producer(tasks)
}

//消费数据处理逻辑
func consumer(task Task) {
    
    
	fmt.Printf("consum task:%v\n", task)
}

func consumerDispatch(tasks chan Task) {
    
    
	defer wg.Done()

	for task := range tasks {
    
    
		consumer(task)
	}
}

func main() {
    
    
	//消费者个数
	var consumerNum = 10
	var channelLen = 50

	tasks := make(chan Task, channelLen)

    //当producer执行完成后关闭队列
	go producerDispatch(tasks)

	for i := 0; i < consumerNum; i++ {
    
    
		wg.Add(1)
        //当consumer消费完后wg.Done()
		go consumerDispatch(tasks)
	}
    //等待全部goroutine完成
	wg.Wait()
	fmt.Println("all done")
}

  1. プロデューサーがタスクを完了すると、close を呼び出してチャネルを閉じます。
  2. チャネルが閉じられているため、コンシューマーはすべてのタスクをフェッチした後、タスクをフェッチするための for ループを終了します。
  3. メインスレッド main は、Wait() を使用して、すべてのタスクが処理された後にのみ終了するようにします。
  4. wg.Add は main に配置する必要があります。それ以外の場合は、main が実行を終了している可能性があります。
  5. ProducerDispatch および ConsumerDispatch コルーチンはまだスケジュールされていないため、「すべて完了」と出力して終了します。これは実際のプロセスにおける落とし穴でもあります。

③上級編

このバージョンには、ロックフリー キューと複数のコルーチン プールを使用して実装された複数のプロデューサーとコンシューマーが含まれています。

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

type Queue struct {
    
    
	items []int
	head  int
	tail  int
}

func NewQueue(size int) *Queue {
    
    
	return &Queue{
    
    make([]int, size), 0, 0}
}

func (q *Queue) Push(item int) bool {
    
    
	next := (q.tail + 1) % len(q.items)
	if next == q.head {
    
    
		return false
	}
	q.items[q.tail] = item
	q.tail = next
	return true
}

func (q *Queue) Pop() (int, bool) {
    
    
	if q.head == q.tail {
    
    
		return 0, false
	}
	item := q.items[q.head]
	q.head = (q.head + 1) % len(q.items)
	return item, true
}

type WorkerPool struct {
    
    
	workers []chan func()
	wg      sync.WaitGroup
}

func NewWorkerPool(nWorker int) *WorkerPool {
    
    
	pool := &WorkerPool{
    
    }
	pool.workers = make([]chan func(), nWorker)
	for i := range pool.workers {
    
    
		pool.workers[i] = make(chan func())
		pool.wg.Add(1)
		go pool.workerLoop(pool.workers[i])
	}
	return pool
}

func (p *WorkerPool) workerLoop(worker chan func()) {
    
    
	defer p.wg.Done()
	for task := range worker {
    
    
		task()
	}
}

func (p *WorkerPool) AddTask(task func()) {
    
    
	worker := p.workers[rand.Intn(len(p.workers))]
	worker <- task
}

func (p *WorkerPool) Close() {
    
    
	for i := range p.workers {
    
    
		close(p.workers[i])
	}
	p.wg.Wait()
}

func produce(queue *Queue, pool *WorkerPool, pid int) {
    
    
	for i := 1; i <= 3; i++ {
    
    
		success := queue.Push(i * pid)
		if !success {
    
    
			fmt.Printf("生产者%d生产失败\n", pid)
		} else {
    
    
			fmt.Printf("生产者%d生产%d\n", pid, i*pid)
			pool.AddTask(func() {
    
    
				time.Sleep(time.Millisecond * time.Duration(rand.Intn(3000)))
			})
		}
	}
}

func consume(queue *Queue, pool *WorkerPool, cid int) {
    
    
	for {
    
    
		item, ok := queue.Pop()
		if !ok {
    
    
			return
		}
		fmt.Printf("消费者%d消费%d\n", cid, item)
		pool.AddTask(func() {
    
    
			time.Sleep(time.Millisecond * time.Duration(rand.Intn(3000)))
		})
	}
}

func main() {
    
    
	queue := NewQueue(5)
	pool := NewWorkerPool(5)

	nProducer := 3
	for i := 1; i <= nProducer; i++ {
    
    
		go produce(queue, pool, i)
	}

	nConsumer := 2
	for i := 1; i <= nConsumer; i++ {
    
    
		go consume(queue, pool, i)
	}

	time.Sleep(time.Second * 10)
	pool.Close()
}

おすすめ

転載: blog.csdn.net/weixin_45565886/article/details/131153381