停止所有在for循环内部的嵌套循环,包括在go函数内部的for循环。

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

Stop all running nested loops inside for -> go func -> for loop

问题

我找到了一种最佳方法来实现“多线程”请求,每次使用不同的代理。这种方法是在一个for循环内嵌套一个go函数和另一个for循环,但我无法找到停止所有循环的方法,就像通常的break语句一样。我尝试了普通的break语句,还尝试了在循环上方添加了out:标签,但都没有停止它。

package main

import (
    "log"
    "encoding/json"
    "github.com/parnurzeal/gorequest"
)

func main(){
    rep := 100 
    for i := 0; i < rep; i++ { 
        log.Println("starting loop")
        go func() { 
            for{
                request := gorequest.New()
                resp, body, errs := request.Get("https://discord.com/api/v9/invites/family").End()
                if errs != nil {
                    return
                }
                if resp.StatusCode == 200{
                    var result map[string]interface{}
                    json.Unmarshal([]byte(body), &result)
                    serverName := result["guild"].(map[string]interface{})["name"]       
                    log.Println(serverName +" response 200, closing all loops")

                    //在这里中断所有循环和goroutine
                }
            }
        }()
    }
    log.Println("response 200, closed all loops")
}
英文:

The best way I figured out how to "multi-thread" requests with a different proxy each time was to nest a go func and for loop inside of another for loop, but I can't figure out how to stop all loops like a break normally would, I tried a regular break also tried break out and added out: above the loops but that didn't stop it.

package main

import (
    &quot;log&quot;
    &quot;encoding/json&quot;
    &quot;github.com/parnurzeal/gorequest&quot;
)

func main(){
    rep := 100 
    for i := 0; i &lt; rep; i++ { 
        log.Println(&quot;starting loop&quot;)
        go func() { 
            for{
                request := gorequest.New()
                resp, body, errs := request.Get(&quot;https://discord.com/api/v9/invites/family&quot;).End()
                if errs != nil {
                    return
                }
                if resp.StatusCode == 200{
                    var result map[string]interface{}
                    json.Unmarshal([]byte(body), &amp;result)
                    serverName := result[&quot;guild&quot;].(map[string]interface{})[&quot;name&quot;]       
                    log.Println(sererName +&quot; response 200, closing all loops&quot;)

                    //break all loops and goroutine here
                }
            }
        }
    }
    log.Println(&quot;response 200,closed all loops&quot;)

答案1

得分: 2

回答这个问题会变得复杂,因为你使用的 parnurzeal/gorequest 包没有提供明显的取消请求的方法(参见此问题)。因为你的重点似乎是过程而不是特定的功能,所以我刚刚使用了标准库 (http)(如果你确实需要使用 gorequest,或许可以提一个关于它的具体问题)。

无论如何,下面的解决方案演示了一些内容:

  • 使用 WaitGroup 来知道所有 goroutine 何时完成(在这里不是必需的,但通常你希望知道是否已经干净地关闭)
  • 通过通道传递结果(从 goroutine 更新共享变量会导致数据竞争
  • 使用 context 进行取消操作。当我们有结果时,调用 cancel 函数将停止进行中的请求。
package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"sync"
)

func main() {
	// 获取上下文和取消函数
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // 这里不是必需的,但良好的实践是确保最终取消上下文。

	results := make(chan string)

	const goRoutineCount = 100
	var wg sync.WaitGroup
	wg.Add(goRoutineCount) // 我们将等待 100 个 goRoutine

	for i := 0; i < goRoutineCount; i++ {
		go func() {
			defer wg.Done() // 当 goRoutine 退出时减少 WaitGroup

			req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://discord.com/api/v9/invites/family", nil)
			if err != nil {
				panic(err)
			}
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				if errors.Is(err, context.Canceled) {
					return // 错误是由于上下文被取消导致的,所以只需关闭
				}
				panic(err)
			}
			defer resp.Body.Close() // 确保关闭 body
			if resp.StatusCode == 200 {
				var result map[string]interface{}
				if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
					panic(err)
				}
				serverName := result["guild"].(map[string]interface{})["name"]
				results <- serverName.(string) // 应该对此进行错误检查...
				cancel()                       // 我们有了结果,所以所有的 goroutine 都可以停止了!
			}
		}()
	}

	// 我们需要处理结果,直到所有的东西都关闭;简单的方法是在完成时关闭通道
	go func() {
		wg.Wait()
		close(results)
	}()

	var firstResult string
	requestsProcessed := 0
	for x := range results {
		fmt.Println("得到结果")
		if requestsProcessed == 0 {
			firstResult = x
		}
		requestsProcessed++ // 可能会得到多个结果(记住请求是并行运行的)
	}

	// 此时所有的 goroutine 都已经关闭
	if requestsProcessed == 0 {
		log.Println("未收到结果")
	} else {
		log.Printf("%s 响应 200,关闭所有循环(已处理的请求:%d)", firstResult, requestsProcessed)
	}
}
英文:

Answering this would be complicated by your use of parnurzeal/gorequest because that package does not provide any obvious way to cancel requests (see this issue). Because your focus appears to be on the process rather than the specific function I've just used the standard library (http) instead (if you do need to use gorequest then perhaps ask a question specifically about that).

Anyway the below solution demonstrates a few things:

  • Uses a Waitgroup so it knows when all go routines are done (not essential here but often you want to know you have shutdown cleanly)
  • Passes the result out via a channel (updating shared variables from a goroutine leads to data races).
  • Uses a context for cancellation. The cancel function is called when we have a result and this will stop in progress requests.
package main
import (
&quot;context&quot;
&quot;encoding/json&quot;
&quot;errors&quot;
&quot;fmt&quot;
&quot;log&quot;
&quot;net/http&quot;
&quot;sync&quot;
)
func main() {
// Get the context and a function to cancel it
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Not really required here but its good practice to ensure context is cancelled eventually.
results := make(chan string)
const goRoutineCount = 100
var wg sync.WaitGroup
wg.Add(goRoutineCount) // we will be waiting on 100 goRoutines
for i := 0; i &lt; goRoutineCount; i++ {
go func() {
defer wg.Done() // Decrement WaitGroup when goRoutine exits
req, err := http.NewRequestWithContext(ctx, http.MethodGet, &quot;https://discord.com/api/v9/invites/family&quot;, nil)
if err != nil {
panic(err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
if errors.Is(err, context.Canceled) {
return // The error is due to the context being cancelled so just shutdown
}
panic(err)
}
defer resp.Body.Close() // Ensure body is closed
if resp.StatusCode == 200 {
var result map[string]interface{}
if err = json.NewDecoder(resp.Body).Decode(&amp;result); err != nil {
panic(err)
}
serverName := result[&quot;guild&quot;].(map[string]interface{})[&quot;name&quot;]
results &lt;- serverName.(string) // Should error check this...
cancel()                       // We have a result so all goroutines can stop now!
}
}()
}
// We need to process results until everything has shutdown; simple approach is to just close the channel when done
go func() {
wg.Wait()
close(results)
}()
var firstResult string
requestsProcessed := 0
for x := range results {
fmt.Println(&quot;got result&quot;)
if requestsProcessed == 0 {
firstResult = x
}
requestsProcessed++ // Possible that we will get more than one result (remember that requests are running in parallel)
}
// At this point all goroutines have shutdown
if requestsProcessed == 0 {
log.Println(&quot;No results received&quot;)
} else {
log.Printf(&quot;xx%s response 200, closing all loops (requests processed: %d)&quot;, firstResult, requestsProcessed)
}
}

huangapple
  • 本文由 发表于 2022年9月8日 08:58:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/73642730.html
匿名

发表评论

匿名网友

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

确定