Golang并发程序中的Bug排查与解决方法

Golang并发程序中的Bug排查与解决方法 我想让 apihandle 这两个函数并发工作,但程序运行不正常,我该如何修复它?我还需要说明,我不能将 api 函数移到 handle 函数的循环中(因为在主项目中,handle 函数是 net/http 标准库的一部分)。

package main

func api() {
        println("api")
}

func handle() {
        for {
                println("handle")
        }
}

func main() {
        for {
                go api()
                go handle()
        }
}

请告诉我,我的做法是否完全错误!


更多关于Golang并发程序中的Bug排查与解决方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

我明白你的意思。

更多关于Golang并发程序中的Bug排查与解决方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我知道这会很快耗尽内存,但主程序必须永远这样做!

实际上,这个项目变得非常奇怪。总的来说,我想做以下事情: 首先,端点及其服务必须可用 其次,在服务可用的同时处理一个API

这就是为什么我想到了并发,但它变得非常困难。

我认为将 main 函数改为以下形式会更有益处:

func main() {
    go workWithApi()
    handleRequests()
}

这样可以在独立的 goroutine 中启动 workWithApi,然后让主 goroutine 运行 http.ListenAndServe

你想做什么?

按照程序现在的写法,api 会打印 "api" 然后返回,所以即使你不断地生成 api 协程,它们最终还是会终止。另一方面,handle 自身运行着一个无限循环,所以 main 函数中 for 循环的每一次迭代都会产生一个新的、打印 "handle" 且永远不会返回的 goroutine,因此这个程序会很快耗尽你的所有内存。

我想,我会把这个问题看作更像是:

package main

func api() {
        println("api")
        http.ListenAndServe(http.HandlerFunc(handle))
}

func handle(w http.ResponseWriter, r *http.Request) {
        println("handle")
}

func main() {
        // TODO: 当有更多后台任务需要从main启动时,将其放入一个goroutine中。
        // 我们暂时保持它是一个普通的函数调用,这样main函数就不会返回。
        api()
}

我们需要更多关于您尝试实现的目标的信息,以便提供更好的反馈。您目前的代码无法工作,并且似乎没有任何实际用途:您不能在一个没有同步机制的无限 for 循环中启动 goroutine。这与做类似下面的事情是一样的:

package main

func main() {
    var vs []int
    for {
        vs = append(vs, 0)
    }
}

它会尽可能多地分配内存,然后导致 panic。让您的 main 函数在无限 for 循环中启动 goroutine 也是一样的;这不会产生任何有用的结果。

我们能否获得更多关于这个 API 的信息?当我想到 API 时,我想到的是一个等待请求到来并在请求到来时进行处理的服务。您使用这个 api 的方式在我看来更像是一个工作进程:它启动,执行一些工作(例如 println("api")),然后返回。然而,对于工作进程,通常会有一些同步机制来限制 goroutine 的数量,否则它们会耗尽您计算机上的所有资源。

我们绝对需要更多关于 handle 函数目的的信息。您不能在一个无限循环中启动 goroutine,而这些 goroutine 本身又处于永不停止的无限循环中;这与我上面提到的向切片追加元素是一样的:唯一的结果就是内存耗尽。

我认为主项目的代码能更好地表达我的意思。

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/mtarif98/go-gateio/database"
	"github.com/mtarif98/go-gateio/json"
)

func getJson() []byte {
	resp, err := http.Get("https://data.gateapi.io/api2/1/tickers")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	bytes, _ := ioutil.ReadAll(resp.Body)
	return bytes
}

func handleCreate(w http.ResponseWriter, req *http.Request) {
	data := gjson.DecodeJson(req)
	fmt.Println(data)
}

func workWithApi() {
	data := getJson()

	var config database.DBData
	database.LoadDBData(&config)
	targetDB := fmt.Sprintf(
		"%s:%s@tcp(%s:%s)/%s",
		config.DBUsername,
		config.DBPassword,
		config.DBAddress,
		config.DBPort,
		config.DBName,
	)

	cryptosFiltered := gjson.GetEssentialInformation(data)
	database.CreateRecords(cryptosFiltered, targetDB) // for now CreateRecords function write a simple log - TEMPORARY
}

func handleRequests() {
	http.HandleFunc("/api/v1/user/create/", handleCreate)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func main() { // 这里我希望 handleRequests() 和 workWithApi 能并发工作,我的意思是,当程序在Web上运行时,它同时也在与API交互。
	handleRequests() // 处理请求
	workWithApi() // 处理API
}

你的代码中存在两个主要问题:无限循环和缺乏并发控制机制。以下是修复后的版本:

package main

import (
    "sync"
    "time"
)

func api() {
    println("api")
}

func handle() {
    for {
        println("handle")
        time.Sleep(1 * time.Second) // 避免CPU占用过高
    }
}

func main() {
    var wg sync.WaitGroup
    
    // 启动handle协程(只需要一个)
    wg.Add(1)
    go func() {
        defer wg.Done()
        handle()
    }()
    
    // 在主循环中启动api协程
    for i := 0; i < 5; i++ { // 限制循环次数,避免无限创建协程
        wg.Add(1)
        go func() {
            defer wg.Done()
            api()
        }()
        time.Sleep(500 * time.Millisecond) // 控制api调用频率
    }
    
    wg.Wait()
}

或者更接近你原始结构的版本:

package main

import (
    "sync"
    "time"
)

func api() {
    println("api")
}

func handle() {
    for {
        println("handle")
        time.Sleep(1 * time.Second)
    }
}

func main() {
    var wg sync.WaitGroup
    
    // 启动handle协程
    wg.Add(1)
    go func() {
        defer wg.Done()
        handle()
    }()
    
    // 使用select控制主循环
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop()
    
    for i := 0; i < 5; i++ {
        <-ticker.C
        wg.Add(1)
        go func() {
            defer wg.Done()
            api()
        }()
    }
    
    wg.Wait()
}

关键修复点:

  1. 使用sync.WaitGroup确保协程正确同步
  2. 为无限循环添加延迟避免CPU爆满
  3. 控制协程创建数量,避免内存泄漏
  4. handle()函数只需要启动一次,而不是在每次循环中都创建新协程
回到顶部