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")
}
- プロデューサーがタスクを完了すると、close を呼び出してチャネルを閉じます。
- チャネルが閉じられているため、コンシューマーはすべてのタスクをフェッチした後、タスクをフェッチするための for ループを終了します。
- メインスレッド main は、Wait() を使用して、すべてのタスクが処理された後にのみ終了するようにします。
- wg.Add は main に配置する必要があります。それ以外の場合は、main が実行を終了している可能性があります。
- 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()
}