go语法--基础41--并发编程--案例4

Go并发编程实践

一、控制时间的高并发模型

可以测试接口在持续高并发情况下是否有句柄泄露

1.1、代码

package main

import (
	"runtime"
	"sync/atomic"
	"time"
)

//5秒超时
var timeout2 = time.Now().Add(time.Second * 5)

//cpu数量
var cpuNum = runtime.NumCPU()

//无缓存
var waitForAll = make(chan struct{})

//无缓存
var done = make(chan struct{})

// 有缓存的管道,空闲的资源
var freeResource = make(chan struct{}, cpuNum)

var doneNum int64

func RunScenario2() {
	// init free resource
	initFreeResource()

	go releaseResource()

	go useResource()
	//waitForAll 沒有数据会阻塞
	// 等待所有的事情完成
	<-waitForAll
}

// init free resource
func initFreeResource() {
	for i := 0; i < cpuNum; i++ {
		freeResource <- struct{}{}
	}
}

//use free resource to do something
func useResource() {
	for {
		// use free resource
		<-freeResource
		go func() {
			time.Sleep(time.Second * 1)
			println("使用空闲资源做一些事:", atomic.AddInt64(&doneNum, 1))
			// done something +1
			done <- struct{}{}
		}()
	}
}

func releaseResource() {
	for time.Now().Before(timeout2) {
		//done 沒有数据会阻塞
		<-done
		// 资源数+1
		freeResource <- struct{}{}
	}
	//超时 后
	//waitForAll  有数据会阻塞
	waitForAll <- struct{}{}
}

func main() {
	RunScenario2()
}
输出
使用空闲资源做一些事: 1
使用空闲资源做一些事: 2
使用空闲资源做一些事: 3
使用空闲资源做一些事: 5
使用空闲资源做一些事: 7
使用空闲资源做一些事: 8
使用空闲资源做一些事: 4
使用空闲资源做一些事: 6
使用空闲资源做一些事: 9
使用空闲资源做一些事: 13
使用空闲资源做一些事: 12
使用空闲资源做一些事: 15
使用空闲资源做一些事: 16
使用空闲资源做一些事: 14
使用空闲资源做一些事: 11
使用空闲资源做一些事: 10
使用空闲资源做一些事: 17
使用空闲资源做一些事: 18
使用空闲资源做一些事: 19
使用空闲资源做一些事: 21
使用空闲资源做一些事: 24
使用空闲资源做一些事: 23
使用空闲资源做一些事: 20
使用空闲资源做一些事: 22
使用空闲资源做一些事: 25
使用空闲资源做一些事: 26
使用空闲资源做一些事: 27
使用空闲资源做一些事: 30
使用空闲资源做一些事: 31
使用空闲资源做一些事: 29
使用空闲资源做一些事: 32
使用空闲资源做一些事: 28
使用空闲资源做一些事: 33
使用空闲资源做一些事: 34

 

二、基于大数据量的并发模型

前面说的基于时间的并发模型,那如果只知道数据量很大,但是具体结束时间不确定,该怎么办呢?

比如,客户给了个几TB的文件列表,要求把这些文件从存储里删除。再比如,实现个爬虫去爬某些网站的所有内容。

而解决此类问题,最常见的就是使用工作池模式了(Worker Pool)。

以删文件为例,我们可以简单这样来处理

在这里插入图片描述

  • Jobs 可以从文件列表里读取文件,初始化为任务,然后发给worker
  • Worker 拿到任务开始做事
  • Collector 收集worker处理后的结果
  • Worker Pool 控制并发的数量

2.1、代码

package main

import (
	"fmt"
	"runtime"
	"strconv"
	"sync"
	"time"
)

var cpuNum = runtime.NumCPU()
var taskTool = 5
var jobs = make(chan int, taskTool)
var results = make(chan string, taskTool)
var wg sync.WaitGroup
var totalTasks = 20 // 本例就要从文件列表里读取
func RunScenario3() {

	for i := 0; i < cpuNum; i++ {
		wg.Add(1)
		//run task
		go runTask(i, jobs, results, &wg)
	}

	wg.Add(1)
	go showResult()

	initJobs()

	close(jobs)
	wg.Wait()
}

func showResult() {
	defer wg.Done()
	for i := 0; i < totalTasks; i++ {
		result := <-results
		fmt.Println("任务结果:", result)
	}
	close(results)
}

func initJobs() {
	for i := 0; i < totalTasks; i++ {
		jobs <- i
	}
}

//run task
func runTask(taskId int, jobs <-chan int, results chan<- string, wg *sync.WaitGroup) {
	defer wg.Done()
	for job := range jobs {

		res := "完成了任务--->job=" + strconv.Itoa(job) + ",taskId=" + strconv.Itoa(taskId)

		fmt.Println("Worker run task", taskId, res)
		time.Sleep(time.Millisecond * time.Duration(100))
		results <- res
	}
}
func main() {
	RunScenario3()
}


输出
Worker run task 3 完成了任务--->job=3,taskId=3
Worker run task 7 完成了任务--->job=4,taskId=7
Worker run task 4 完成了任务--->job=5,taskId=4
Worker run task 5 完成了任务--->job=7,taskId=5
Worker run task 6 完成了任务--->job=6,taskId=6
Worker run task 0 完成了任务--->job=0,taskId=0
Worker run task 1 完成了任务--->job=1,taskId=1
Worker run task 2 完成了任务--->job=2,taskId=2
Worker run task 0 完成了任务--->job=8,taskId=0
Worker run task 5 完成了任务--->job=11,taskId=5
Worker run task 4 完成了任务--->job=10,taskId=4
任务结果: 完成了任务--->job=0,taskId=0
任务结果: 完成了任务--->job=4,taskId=7
任务结果: 完成了任务--->job=5,taskId=4
Worker run task 6 完成了任务--->job=12,taskId=6
任务结果: 完成了任务--->job=7,taskId=5
任务结果: 完成了任务--->job=6,taskId=6
任务结果: 完成了任务--->job=3,taskId=3
Worker run task 7 完成了任务--->job=9,taskId=7
Worker run task 1 完成了任务--->job=15,taskId=1
任务结果: 完成了任务--->job=2,taskId=2
任务结果: 完成了任务--->job=1,taskId=1
Worker run task 2 完成了任务--->job=14,taskId=2
Worker run task 3 完成了任务--->job=13,taskId=3
Worker run task 3 完成了任务--->job=16,taskId=3
Worker run task 1 完成了任务--->job=17,taskId=1
Worker run task 7 完成了任务--->job=19,taskId=7
Worker run task 2 完成了任务--->job=18,taskId=2
任务结果: 完成了任务--->job=13,taskId=3
任务结果: 完成了任务--->job=15,taskId=1
任务结果: 完成了任务--->job=14,taskId=2
任务结果: 完成了任务--->job=9,taskId=7
任务结果: 完成了任务--->job=12,taskId=6
任务结果: 完成了任务--->job=8,taskId=0
任务结果: 完成了任务--->job=10,taskId=4
任务结果: 完成了任务--->job=11,taskId=5
任务结果: 完成了任务--->job=19,taskId=7
任务结果: 完成了任务--->job=16,taskId=3
任务结果: 完成了任务--->job=18,taskId=2
任务结果: 完成了任务--->job=17,taskId=1

在Go里,分发任务,收集结果,我们可以都交给Channel来实现。从实现上更加的简洁。

仔细看会发现,本模型也是适用于按时间来控制并发。只要把totalTask的遍历换成时间控制就好了。

三、等待异步任务执行结果

3.1、代码

package main

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

func RunScenario4() {
	input := make(chan int)
	output := make(chan int)
	go func() {
		id := rand.Intn(100)
		for {
			input <- id
		}
	}()
	go func() {
		for {
			output <- <-input
		}
	}()

	select {
	case out := <-output:
		fmt.Println("output:", out)
	case <-time.After(time.Duration(30 * time.Second)):
		fmt.Errorf("指定时间内都没有得到结果")
	default:
		fmt.Errorf("默认报错")
	}

}
func main() {
	RunScenario4()
}


输出

output: 81

在select的case情况,加上time.After()模型可以让我们在一定时间范围内等待异步任务结果,防止程序卡死。

四、定时反馈异步任务结果

上面我们说到持续的压测某后端API,但并未实时收集结果。而很多时候对于性能测试场景,实时的统计吞吐率,成功率是非常有必要的。

4.1、代码

package main

import (
	"fmt"
	"time"
)

func RunScenario5() {

	go func() {
		for {
			time.Sleep(time.Second * 1)
			fmt.Println("do something")
		}
	}()

	t := time.NewTicker(time.Second * 3)
	for {
		select {
		case <-t.C:
			// 计算并打印实时数据
			fmt.Println("每隔3秒获取结果数据")
		}
	}
}
func main() {
	RunScenario5()
}

输出

do something
do something
每隔3秒获取结果数据
do something
do something
do something
每隔3秒获取结果数据
do something
do something
do something
每隔3秒获取结果数据
do something
do something
do something
每隔3秒获取结果数据
do something
do something
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值