Go routine线程池&&并发场景


欢迎来到我的博客,更多文章点击这里


并发,并行,串行

部分参考节选自CSDN博客:

通俗理解:
吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。

吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 (不一定是同时的)

吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

普通解释:

  • 并发:交替做不同事情的能力

  • 并行:同时做不同事情的能力

专业术语:

  • 并发:不同的代码块交替执行

  • 并行:不同的代码块同时执行

并发

部分转载自51CTO博客:
部分参考自知乎:

并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。

并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
并行性指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。

  • 并发是一种现象:同时运行多个程序或多个任务需要被处理的现象

  • 这些任务可能是并行执行的,也可能是串行执行的,和CPU核心数无关,是操作系统进程调度和CPU上下文切换达到的结果

  • 解决大并发的一个思路是将大任务分解成多个小任务:

    • 可能要使用一些数据结构来避免切分成多个小任务带来的问题

    • 可以多进程/多线程并行的方式去执行这些小任务达到高效率

    • 或者以单进程/单线程配合多路复用执行这些小任务来达到高效率

并行

部分转载自博客:

并行性指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。

<注意>:

  • 如果是单进程/单线程的并行,那么效率比串行更差

  • 如果只有单核cpu,多进程并行并没有提高效率

  • 从任务队列上看,由于同时从队列中取得多个任务并执行,相当于将一个长任务队列变成了短队列

串行

一次只能取得一个任务并执行这一个任务

协程池DEMO

框架图

代码

package main

import (
	"fmt"
	"time"
)

type Task struct {
    
    
	f func() error
}

// 创建一个任务
func NewTask(f func() error) *Task {
    
    
	return &Task{
    
    f: f}
}

//  执行任务
func (t *Task) Excute() {
    
    
	t.f()
}

// ------------------Go routine------------------
// Language: go
type Pool struct {
    
    
	// 对外提供的接口
	EntryChannel chan *Task
	// 内部Task队列
	JobsChannel chan *Task
	// 协程池最大worker数量
	workerNum int
}

// 创建一个协程池
func NewPool(worker int) *Pool {
    
    
	return &Pool{
    
    
		EntryChannel: make(chan *Task),
		JobsChannel:  make(chan *Task),
		workerNum:    worker,
	}
}

//
func (p *Pool) Worker(i int) {
    
    
	for i := 0; i < p.workerNum; i++ {
    
    
		go func() {
    
    
			for {
    
    
				select {
    
    
				case job := <-p.JobsChannel:
					job.Excute()
					fmt.Println("workerID", i, "excute task finished")
				}
			}
		}()
	}
	// for task := range p.JobsChannel {
    
    
	// 	// 一旦任务队列有任务,就执行任务
	// 	task.Excute()
	// 	fmt.Println("workerID", i, "excute task finished")
	// }
}

// ------------------协程池工作------------------
func (p *Pool) Run() {
    
    
	// 根据worker数量创建worker
	// 从EntryChannel中获取任务, 并将任务放入JobsChannel
	for i := 0; i < p.workerNum; i++ {
    
    
		// 每一个worker都应该是一个go routine
		go p.Worker(i)
	}

	for task := range p.EntryChannel {
    
    
		// 如果读取到task,就将task放入JobsChannel
		p.JobsChannel <- task
	}
}

func main() {
    
    
	// 创建一个任务
	t1 := NewTask(task01)
	t2 := NewTask(task02)
	// 创建一个协程池
	pool := NewPool(10)
	totalNum := 0
	go func() {
    
    
		for {
    
    
			pool.EntryChannel <- t1
			pool.EntryChannel <- t2
			totalNum += 1
			fmt.Println("totalNum:", totalNum)
		}
	}()
	pool.Run()
}

func task01() error {
    
    
	fmt.Println("task01", time.Now().Unix())
	return nil
}
func task02() error {
    
    
	fmt.Println("task02", time.Now().Unix())
	return nil
}

channel通信机制复盘

下面这样会报错,因为·count退出后,c就不能再使用了,main永远接收不到来自channel的数据了,造成死锁

// 下面这样会报错,因为count退出后,c就不能再使用了,main永远接收不到来自channel的数据了,造成死锁
func main() {
    
    
	c := make(chan string)
	go count(5, "羊", c)
	for {
    
    
		message := <-c
		fmt.Println(message)
	}
}

func count(n int, s string, c chan string) {
    
    
	for i := 0; i < n; i++ {
    
    
		c <- s
		time.Sleep(time.Millisecond * 500)
	}
	// close(c)
}

/*
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        D:/Go/learn/goPool/routine.go:13 +0xba
exit status 2
*/

改进呢很简单

// 稍微复杂写法
func main() {
    
    
	c := make(chan string)
	go count(5, "羊", c)
	for {
    
    
		message, open := <-c
		// 加了open的判断,就可以避免死锁,判断channel是否关闭,
		if !open {
    
    
			break
		}
		fmt.Println(message)
	}
}

// 这样写更为简单
func main2() {
    
    
	c := make(chan string)
	go count(5, "羊", c)
	for message := range c {
    
    
		fmt.Println(message)
	}
}

func count(n int, s string, c chan string) {
    
    
	for i := 0; i < n; i++ {
    
    
		c <- s
		time.Sleep(time.Millisecond * 500)
	}
	// 在执行完后主动关闭channel,
	close(c)
}

/*
PS D:\Go\learn\goPool> go run .\routine02Change.go
羊
羊
羊
羊
羊
PS D:\Go\learn\goPool>
*/

并发应用场景(搜索文件)

未使用并发

var (
	matches int
	query   string
)

func main() {
    
    
	start := time.Now()
	query = "main.go"
	search("d:/", true)
	fmt.Println(matches, "matches found")
	fmt.Println(time.Since(start))
}

func search(path string, master bool) {
    
    
	files, err := ioutil.ReadDir(path)
	if err != nil {
    
    
		fmt.Println(err)
		return
	}
	for _, file := range files {
    
    
		name := file.Name()
		if name == query {
    
    
			matches++
			fmt.Println(path + "/" + name)
		}
		if file.IsDir() {
    
    
			fmt.Println("search in", path+"/"+name)
			// 如果worker数量超过了最大值,就直接递归搜索
			search(path+"/"+name, true)
		}
	}
}

使用并发

var (
	matches        int
	query          string
	workerCount    int
	maxWorkerCount int = 12
	searchRequest      = make(chan string)
	workerDone         = make(chan bool)
	foundMatch         = make(chan bool)
)

func main() {
    
    
	start := time.Now()
	query = "main.go"
	workerCount = 1
	go search("d:/", true)
	waitforWorkers()
	fmt.Println(matches, "matches found")
	fmt.Println(time.Since(start))
}

func waitforWorkers() {
    
    
	for {
    
    
		select {
    
    
		case path := <-searchRequest:
			workerCount++
			go search(path, true)
		case <-workerDone:
			workerCount--
			if workerCount == 0 {
    
    
				return
			}
		case <-foundMatch:
			matches++

		}
	}

}
func search(path string, master bool) {
    
    
	files, err := ioutil.ReadDir(path)
	if err != nil {
    
    
		fmt.Println(err)
		return
	}
	for _, file := range files {
    
    
		name := file.Name()
		if name == query {
    
    
			foundMatch <- true
			fmt.Println(path + "/" + name)
		}
		if file.IsDir() {
    
    
			if workerCount < maxWorkerCount {
    
    
				searchRequest <- path + "/" + name
			} else {
    
    
				fmt.Println("search in", path+"/"+name)
				// 如果worker数量超过了最大值,就直接递归搜索
				search(path+"/"+name, true)
			}
			// search(path + name + "/")
		}
	}
	if master {
    
    
		workerDone <- true
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_44154094/article/details/124299734