golang 中的并发控制与优雅结束


提到 golang 并发,就可以联想到下列关键词:

  • goroutine
  • channel
  • sync.WaitGroup
  • sync.Mutex
  • tunny/ants协程池
  • for-select

在一般的场景中,比如碰到这类场景,需要控制并发协程的数量,通常的话,我们可以通过 channel 或者 类似协程池功能,或者就是直接用现成的三方库-协程池,总之这些都是常用的一些方案,所以由此我们大概总结以下几种并发控制的方式。

并发控制

通过 channel 实现信号通知

先看代码:

package main

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

// sync.WaitGroup + channel
func main() {
	// concurrency count
	var concurrency int = 3
	// define sem
	sem := make(chan struct{}, concurrency)
	var wg sync.WaitGroup


	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			worker(id, sem)
		}(i)
	}

	wg.Wait()
	fmt.Println("Main goroutine end.")
}


func worker(id int, sem chan struct{})  {
	// 占用一个信号量
	sem <- struct{}{}
	// 运行结束后,释放信号量
	defer func() {
		<- sem
	}()

	// exec task
	rand.Seed(time.Now().UnixNano())
	time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
	fmt.Printf("Time: %v, Worker %d: working...\n", time.Now().Format(time.RFC3339Nano), id)
}

通过定义一个给定缓冲的 channel 当做一个信号池,根据 channel 缓冲特性及阻塞特性,实现并发数量的限制或者控制。

自定义简单的协程池

先看代码:

package main

import (
	"fmt"
	"sync"
)

func worker(id int, jobs <-chan int, results chan<- int) {
	//jobs等待主要协程往jobs放数据
	for j := range jobs {
		fmt.Printf("协程池 %d: 协程池正在工作 %d\n", id, j)
		results <- j
	}
}

func main() {
	const numJobs = 5    //协程要做的工作数量
	const numWorkers = 3 //协程池数量

	jobs := make(chan int, numJobs)
	results := make(chan int, numJobs)

	var wg sync.WaitGroup

	// 启动协程池
	for i := 1; i <= numWorkers; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			worker(id, jobs, results)
		}(i)
	}

	// 提交任务
	for j := 1; j <= numJobs; j++ {
		jobs <- j
	}

	close(jobs)

	// 等待所有工作完成
	go func() {
		wg.Wait()
		close(results)
	}()

	// 处理结果
	for result := range results {
		fmt.Println("Result:", result)
	}
}

通过现有的 tunny/ants 协程池

package main
 
import (
	"log"
	"time"
 
	"github.com/Jeffail/tunny"
)
 
func main() {
	pool := tunny.NewFunc(10, func(i interface{}) interface{} {
		log.Println(i)
		time.Sleep(time.Second)
		return nil
	})
	defer pool.Close()
 
	for i := 0; i < 500; i++ {
		go pool.Process(i)
	}
	time.Sleep(time.Second * 4)
}

并发协程的优雅结束

for-range

先看代码:

package main

import (
	"fmt"
	"time"
)

// for-range channel
func main() {
	jobs := make(chan int, 10)
	stop := make(chan struct{})
	defer close(stop)

	// publish jobs
	go func() {
		for i := 0; i < 20; i++ {
			jobs <- i
		}
		close(jobs)
	}()
	// exec jobs
	go func(job <- chan int) {
		for num := range job {
			fmt.Printf("Time: %v, exec job: %d\n", time.Now().Format(time.RFC3339Nano), num)
		}
		stop <- struct{}{}
	}(jobs)

	// 等待发送信号结束 主goroutine
	<- stop
	fmt.Println("Main goroutine end.")
}

主要利用 channel 关闭后,通过 for-range 操作自动退出特性实现子协程优雅退出。

for-select

参考:

posted on 2024-07-23 15:39  进击的davis  阅读(72)  评论(0编辑  收藏  举报

导航