NoteConcurrent in for Go loop

huangapple go评论85阅读模式
英文:

NoteConcurrent in for Go loop

问题

我已经编写了一段代码来获取CPU核心的使用百分比。如果某个核心的CPU使用率低于50%,我将运行压力测试代码,以使使用率超过50%。我必须将此策略应用于所有CPU核心。

以下是完整的代码:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
	"github.com/dhoomakethu/stress/utils"
	strip "github.com/grokify/html-strip-tags-go"
)

func main() {
	url := "http://localhost:8080/getCPU"
	resp := Get(url)
	//移除HTML标签
	stripped := strip.StripTags(resp)
	s := strings.Split(stripped, "%")
	var j []int
	for i := 0; i < len(s)-1; i++ {
		number, _ := strconv.Atoi(s[i])
		if number < 50 {
			j = append(j, i)
		}
	}
	fmt.Println(j)
	sampleInterval := 100 * time.Millisecond
	cpuload := 1.0
	duration := 300.0
	for a := 0; a < len(j)/2; a++ {
		cpucore := j[a]
		runCpuLoader(sampleInterval, cpuload, duration, cpucore)
	}
}

//GET请求
func Get(url string) string {
	response, err := http.Get(url)
	if err != nil {
		fmt.Printf("%s", err)
		os.Exit(1)
	} else {
		defer response.Body.Close()
		contents, err := ioutil.ReadAll(response.Body)
		if err != nil {
			fmt.Printf("%s", err)
			os.Exit(1)
		}
		return string(contents)
	}
	return ""
}

//压力测试CPU
func runCpuLoader(sampleInterval time.Duration, cpuload float64, duration float64, cpu int)   {
	controller := utils.NewCpuLoadController(sampleInterval, cpuload)
	monitor := utils.NewCpuLoadMonitor(float64(cpu), sampleInterval)

	actuator := utils.NewCpuLoadGenerator(controller, monitor, time.Duration(duration))
	utils.StartCpuLoadController(controller)
	utils.StartCpuMonitor(monitor)

	utils.RunCpuLoader(actuator)
	utils.StopCpuLoadController(controller)
	utils.StopCpuMonitor(monitor)

}

在代码的这部分:

for a := 0; a < len(j)/2; a++ {
	cpucore := j[a]
	runCpuLoader(sampleInterval, cpuload, duration, cpucore)
}

我希望代码并发地运行压力测试以增加CPU使用百分比。但是runCpuLoader(sampleInterval, cpuload, duration, cpucore)函数的持续时间为5分钟,因此无法并发运行。

注意:

GET函数的输出如下图所示:
NoteConcurrent in for Go loop

英文:

I have written code to get the percent of usage on CPU core. If the CPU usage in one core is lower than 50% I will run stress test code to increase usage percent higher than 50%. I must appy this policy for all CPU cores.

Here is my full code:

package main
import (
&quot;fmt&quot;
&quot;io/ioutil&quot;
&quot;net/http&quot;
&quot;os&quot;
&quot;strconv&quot;
&quot;strings&quot;
&quot;time&quot;
&quot;github.com/dhoomakethu/stress/utils&quot;
strip &quot;github.com/grokify/html-strip-tags-go&quot;
)
func main() {
url := &quot;http://localhost:8080/getCPU&quot;
resp := Get(url)
//remove htlm tag
stripped := strip.StripTags(resp)
s := strings.Split(stripped, &quot;%&quot;)
var j []int
for i := 0; i &lt; len(s)-1; i++ {
number, _ := strconv.Atoi(s[i])
if number &lt; 50 {
j = append(j, i)
}
}
fmt.Println(j)
sampleInterval := 100 * time.Millisecond
cpuload := 1.0
duration := 300.0
for a := 0; a &lt; len(j)/2; a++ {
cpucore := j[a]
runCpuLoader(sampleInterval, cpuload, duration, cpucore)
}
}
//GET
func Get(url string) string {
response, err := http.Get(url)
if err != nil {
fmt.Printf(&quot;%s&quot;, err)
os.Exit(1)
} else {
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
fmt.Printf(&quot;%s&quot;, err)
os.Exit(1)
}
return string(contents)
}
return &quot;&quot;
}
// stress cpu
func runCpuLoader(sampleInterval time.Duration, cpuload float64, duration float64, cpu int)   {
controller := utils.NewCpuLoadController(sampleInterval, cpuload)
monitor := utils.NewCpuLoadMonitor(float64(cpu), sampleInterval)
actuator := utils.NewCpuLoadGenerator(controller, monitor, time.Duration(duration))
utils.StartCpuLoadController(controller)
utils.StartCpuMonitor(monitor)
utils.RunCpuLoader(actuator)
utils.StopCpuLoadController(controller)
utils.StopCpuMonitor(monitor)
}

In this part of code

for a := 0; a &lt; len(j)/2; a++ {
cpucore := j[a]
runCpuLoader(sampleInterval, cpuload, duration, cpucore)
}
}

I want the code concurrent run stress test to increase percent of CPU usage. But the function runCpuLoader(sampleInterval, cpuload, duration, cpucore) have duration in 5 minutes so it cannot run as concurrent.

Note:

Output of GET function like image bellow:
NoteConcurrent in for Go loop

答案1

得分: 1

你应该完成Go之旅,并特别关注Go协程,还可以查看等待组。我将在这里简要介绍适用的概念。

go关键字

在Go中,同时运行函数非常简单。给定以下代码,我只需要在blockingCode前面添加go前缀,就可以使其与程序的其余部分同时运行。

func blockingCode() {
    time.Sleep(time.Second)
    fmt.Println("finishing blocking")
}

func main() {
    fmt.Println("starting main")
    go blockingCode()
    fmt.Println("finishing main")
}

你会注意到以下几点:

  • 如果我删除go前缀,该函数将会阻塞,你将看到所有三个打印语句,后两个打印语句比第一个打印语句晚一秒钟(go playground)。
  • 如果我重新添加go前缀,该函数将不会阻塞,但你将看不到fmt.Println("finishing blocking")的打印语句。这是因为主函数(主Go协程)在blockingCode完成之前终止。这就是等待组的作用所在。

WaitGroup

为了等待多个Go协程完成,我们可以使用等待组(wait group)。https://gobyexample.com/waitgroups

将等待组视为原子计数器,调用wg.Wait会阻塞,直到计数器为0。当你创建Go协程时,增加计数器(wg.Add),当你完成Go协程时,减少计数器(wg.Done)。以下是支持等待组的代码示例(go playground)。

func blockingCode(wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(time.Second)
    fmt.Println("finishing blocking")
}

func main() {
    wg := sync.WaitGroup{}
    fmt.Println("starting main")
    wg.Add(1)
    go blockingCode(&wg)
    wg.Wait()
    fmt.Println("finishing main")
}

现在,这将产生与上面相同的行为 - 该函数将阻塞,你将看到所有三个打印语句,后两个打印语句比第一个打印语句晚一秒钟。那么我们为什么要将其并发执行呢?区别在于,现在我们可以同时运行许多事情,而不是按顺序运行许多事情。之所以在这种情况下看不到并发的好处,是因为我们只是同时执行了一件事情。

让我们将我们的示例调整为多次运行blockingCode。你会注意到,即使我们调用了需要一秒钟才能运行的blockingCode三次,该脚本运行的时间几乎相同。如果我们删除go关键字和等待组,我们将看到该代码至少需要三秒钟才能运行(go playground)。

func blockingCode(wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(time.Second)
    fmt.Println("finishing blocking")
}

func main() {
    wg := sync.WaitGroup{}
    fmt.Println("starting main")
    wg.Add(1)
    go blockingCode(&wg)
    wg.Add(1)
    go blockingCode(&wg)
    wg.Add(1)
    go blockingCode(&wg)
    wg.Wait()
    fmt.Println("finishing main")
}

你的示例

如何处理并发行为取决于你的应用程序的具体情况,但是像这样的代码应该实现了我在这里提供的并发概念,并允许你将监视器与加载器并发运行。

func main() {
    // ...
    wg := sync.WaitGroup{}
    for a := 0; a < len(j)/2; a++ {
        cpucore := j[a]
        wg.Add(1)
        go runCpuLoader(&wg, sampleInterval, cpuload, duration, cpucore)
    }
    wg.Wait()
}

func runCpuLoader(wg *sync.WaitGroup, sampleInterval time.Duration, cpuload float64, duration float64, cpu int) {
    defer wg.Done()
    // ...
}
英文:

You should complete the Tour of Go and focus specifically on go routines as well as check out wait groups. I'll cover the applicable concepts briefly here.

The go keyword

In go, running functions concurrently is extremely easy. Given the following code, I need only add the go prefix in front of blockingCode to make it run concurrently with the rest of the program.

func blockingCode() {
time.Sleep(time.Second)
fmt.Println(&quot;finishing blocking&quot;)
}
func main() {
fmt.Println(&quot;starting main&quot;)
go blockingCode()
fmt.Println(&quot;finishing main&quot;)
}

You'll notice a couple things about this:

  • If I remove the go prefix, the function will block and you will see all three print statements, the last two being printed a second later than the first one (go playground).
  • If I add the go prefix back in, the function will not block, but you will not see the fmt.Println(&quot;finishing blocking&quot;) print statement. This is because the main function (the main go-routine) terminates before blockingCode finishes. This is where wait groups come into play.

WaitGroup

> To wait for multiple goroutines to finish, we can use a wait group. https://gobyexample.com/waitgroups.

Think of a wait group as an atomic counter, where calls to wg.Wait block until the counter is 0. As you spawn go-routines, you increment the counter (wg.Add), as you finish go-routines, you de-increment the counter (wg.Done). Here is the code with support for wait groups. go playground

func blockingCode(wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(time.Second)
fmt.Println(&quot;finishing blocking&quot;)
}
func main() {
wg := sync.WaitGroup{}
fmt.Println(&quot;starting main&quot;)
wg.Add(1)
go blockingCode(&amp;wg)
wg.Wait()
fmt.Println(&quot;finishing main&quot;)
}

Now this results in the exact same behavior we saw above -- the function will block and you will see all three print statements, the last two being printed a second later than the first -- so why did we make this concurrent in the first place? The difference is, we can run many things concurrently now, rather than many things sequentially. The reason we don't see the benefits of concurrency in this case is because we're only doing one thing concurrently.

Let's adapt our example to run blockingCode several times. What you'll notice is that the script takes virtually the same amount of time to run, even though we're calling our blockingCode that takes a full second to run, three times, if we remove the go keyword and the wait groups, we would see this code take at least three seconds to run go playground.

func blockingCode(wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(time.Second)
fmt.Println(&quot;finishing blocking&quot;)
}
func main() {
wg := sync.WaitGroup{}
fmt.Println(&quot;starting main&quot;)
wg.Add(1)
go blockingCode(&amp;wg)
wg.Add(1)
go blockingCode(&amp;wg)
wg.Add(1)
go blockingCode(&amp;wg)
wg.Wait()
fmt.Println(&quot;finishing main&quot;)
}

Your example

How you choose to handle concurrent behavior depends a lot on the specifics of your application, however something like this should implement the concurrency concepts I provided here and allow you to run your monitors concurrently with your loaders.

func main() {
// ...
wg := sync.WaitGroup{}
for a := 0; a &lt; len(j)/2; a++ {
cpucore := j[a]
wg.Add(1)
go runCpuLoader(&amp;wg, sampleInterval, cpuload, duration, cpucore)
}
wg.Wait()
}
func runCpuLoader(wg *sync.WaitGroup, sampleInterval time.Duration, cpuload float64, duration float64, cpu int) {
defer wg.Done()
// ...
}

huangapple
  • 本文由 发表于 2021年7月6日 21:36:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/68271582.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定