Nutzen Sie gleichzeitige Programmierung – Timer, Ticker, WaitGroup und andere gängige Modelle

Gehen Sie zur gleichzeitigen Programmierung – Timer-, Ticker- und WaitGroup-Nutzung

1 Timer (einmal ausführen)

1.1 Konzept

Wenn Sie eine Aufgabe nach einer bestimmten Zeit ausführen müssen, können Sie time.Timer verwenden. Der Timer sendet nach einer gewissen Zeit einen Zeitwert an einen Kanal, der zum Auslösen der Ausführung von Aufgaben verwendet werden kann. Insbesondere wenn Sie eine Aufgabe nach einer bestimmten Zeit ausführen müssen, können Sie einen Timer erstellen und dann die Anweisung <-timer.C verwenden, um den Zeitwert aus dem Timer.C-Kanal zu lesen. Wenn der Zeitwert gelesen wird, Aufgabe ausführen.

1.2 Nutzung

package main

import (
	"fmt"
	"time"
)

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

Ergebnis:

timer expired

2 Ticker (wird mehrmals regelmäßig ausgeführt)

2.1 Konzept

Wenn Sie eine Aufgabe regelmäßig ausführen müssen, können Sie time.Ticker verwenden. Der Ticker sendet regelmäßig einen Zeitwert an einen Kanal, der zum Auslösen der Ausführung periodischer Aufgaben verwendet werden kann. Insbesondere wenn Sie eine bestimmte Aufgabe regelmäßig ausführen müssen, können Sie einen Ticker erstellen und dann eine for-Range-Schleife verwenden, um den Zeitwert aus dem Kanal Ticker.C zu lesen. Immer wenn der Zeitwert gelesen wird, wird die reguläre Aufgabe ausgeführt.

2.2 Nutzung

package main

import (
	"fmt"
	"time"
)

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

Ergebnis:

tick
tick
tick
tick
...

3 WaitGroup (warten Sie, bis alle Goroutinen abgeschlossen sind)

3.1 Konzept

Wenn Sie auf den Abschluss einer Gruppe von Goroutinen warten müssen, können Sie sync.WaitGroup verwenden. WaitGroup wird verwendet, um darauf zu warten, dass eine Gruppe von Goroutinen ihre Arbeit abschließt. Wenn eine Goroutine insbesondere auf den Abschluss einer Gruppe anderer Goroutinen warten muss, kann sie die Add-Methode von WaitGroup aufrufen, um den Zählerwert zu erhöhen und dann die Gruppe von Goroutinen zu starten. Wenn jede Goroutine abgeschlossen ist, sollte die Done-Methode aufgerufen werden, um den Zähler zu dekrementieren. Während Sie darauf warten, dass dieser Satz von Goroutinen abgeschlossen ist, sollte schließlich die Wait-Methode aufgerufen werden, die blockiert, bis der Zähler 0 erreicht.

3.2 Nutzung

Grundlegende Verwendung:

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 andere

4.1 Golang implementiert das Producer-Consumer-Modell

① Einfache Version

Diese Version enthält nur einen Produzenten und einen Konsumenten und verwendet Goroutine und Kanal, um die Nachrichtenübermittlung zu implementieren.

  • Die Variable done wird verwendet, um im Hauptthread das Warten auf das Ende aller Unter-Coroutinen zu blockieren. Während der Ausführung des Codes wird ein Signal an die Variable done (done <- true) gesendet, um anzuzeigen, dass eine Sub-Coroutine die Aufgabe abgeschlossen hat. Wenn alle Sub-Coroutinen ihre Aufgaben abgeschlossen haben, blockiert die Ausführung von <-done den Hauptthread und wartet darauf, dass alle Sub-Coroutinen ihre Aufgaben abgeschlossen haben. Beim Produzieren und Konsumieren wird „Done“ nur für einfache Kommunikationsaufgaben verwendet und blockiert nicht den Hauptthread.
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
}

②Erweiterte Version

Diese Version enthält mehrere Produzenten und Konsumenten, implementiert mithilfe gepufferter Kanäle und Wartegruppen.

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

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

Code:

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. Nachdem der Produzent die Aufgabe abgeschlossen hat, ruft er close auf, um den Kanal zu schließen.
  2. Da der Kanal geschlossen ist, verlässt der Verbraucher die for-Schleife zum Abrufen von Aufgaben, nachdem er alle Aufgaben abgerufen hat.
  3. Der Hauptthread main verwendet Wait(), um sicherzustellen, dass er erst beendet wird, nachdem alle Aufgaben verarbeitet wurden.
  4. wg.Add muss in main platziert werden. Andernfalls ist die Ausführung von main möglicherweise abgeschlossen.
  5. Die Coroutinen „produzentDispatch“ und „consumerDispatch“ wurden noch nicht geplant, daher geben sie einfach „Alles erledigt“ aus und werden beendet. Dies ist auch eine Falle im eigentlichen Prozess.

③Erweiterte Version

Diese Version enthält mehrere Produzenten und Konsumenten, die mithilfe von sperrenfreien Warteschlangen und mehreren Coroutine-Pools implementiert werden.

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()
}

Supongo que te gusta

Origin blog.csdn.net/weixin_45565886/article/details/131153381
Recomendado
Clasificación