【问题标题】:Go Routine: Shared Global variable in web serverGo Routine:Web 服务器中的共享全局变量
【发布时间】:2019-07-04 14:17:49
【问题描述】:

我已经在端口上运行 Web 服务器并处理发布请求,该请求在内部调用不同的 url 以使用 goroutine 获取响应并继续。

我已将整个流程划分为不同的方法。代码草稿。

package main

import (
    "bytes"
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
    "time"
)

var status_codes string

func main() {

    router := mux.NewRouter().StrictSlash(true)
    /*router := NewRouter()*/
    router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        _, _ = fmt.Fprintf(w, "Hello!!!")
    })

    router.HandleFunc("/{name}", func(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
        prepare(w, r, vars["name"])

    }).Methods("POST")

    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", 8080), router))

}

func prepare(w http.ResponseWriter, r *http.Request, name string) {
    //initializing for the current request, need to maintain this variable for each request coming
    status_codes = ""

    //other part of the code and call to goroutine
    var urls []string
    //lets say all the url loaded, call the go routine func and wait for channel to respond and then proceed with the response of all url
    results := callUrls(urls)
    process(w, results)

}

type Response struct {
    status          int
    url             string
    body            string
}

func callUrls(urls []string) []*Response {
    ch := make(chan *Response, len(urls))
    for _, url := range urls {
        go func(url string) {
            //http post on url,
            //base on status code of url call, add to status code
            //some thing like

            req, err := http.NewRequest("POST", url, bytes.NewBuffer(somePostData))
            req.Header.Set("Content-Type", "application/json")
            req.Close = true

            client := &http.Client{
                Timeout: time.Duration(time.Duration(100) * time.Second),
            }

            response, err := client.Do(req)

            if err != nil {
                status_codes += "200,"
                //do other thing with the response received
            } else {
                status_codes += "500,"

            }

            // return to channel accordingly
            ch <- &Response{200, "url", "response body"}

        }(url)
    }
    var results []*Response
    for {
        select {
        case r := <-ch:
            results = append(results, r)
            if len(results) == len(urls) {
                //Done
                close(ch)
                return results
            }

        }
    }
}

func process(w http.ResponseWriter, results []*Response){
    //read those status code received from all urls call for the given request
    fmt.Println("status", status_codes)

    //Now the above line keep getting status code from other request as well
    //for eg. if I have called 5 urls then it should have
    //200,500,204,404,200,

    //but instead it is 
    //200,500,204,404,200,204,404,200,204,404,200, and some more keep growing with time
}

上面的代码是这样的:

  1. 全局变量声明,在准备函数中初始化。
  2. 在 goroutine callUrls 函数中附加值
  3. 在处理函数中读取那些变量

现在我应该将那些全局声明的变量传递给每个函数调用以使其成为本地变量,因为那时它不会被共享吗?(我不想这样做。)

或者有没有其他方法可以在不向被调用函数添加更多参数的情况下实现相同的目标。

因为我将有很少的其他字符串和 int 值将在整个程序和 go 例程函数中使用。

使它们成为线程安全的正确方法是什么,并且每个请求同时在端口上只有 5 个代码。

【问题讨论】:

  • "现在我应该将那些全局声明的变量传递给每个函数调用以使它们成为本地变量,因为那时它不会被共享吗?" - 这个,加上+= 确实会在本地进行更改(并且毫无意义)。
  • 如果你修改全局状态,你需要围绕它进行一些同步(互斥锁等)
  • 我的意思是将它传递给每个函数就像在准备函数status_codes := "" results := callUrls(urls, status_codes) process(w, results, status_codes ]) 中一样,因此在 callUrls 中将附加一个空变量,然后它可以在过程函数中使用。但正如我所说,我不想这样做。我遇到 Context 不确定这是否是我需要使用的东西。
  • "然后就可以在进程函数中使用"——可以,但是还是空的。
  • 那么正确的方法是什么?

标签: go


【解决方案1】:

不要使用全局变量,而是使用显式变量并使用函数参数。此外,status_codes 上存在竞争条件,因为它被多个 goroutine 访问,而没有任何互斥锁。

看看我下面的修复。

func prepare(w http.ResponseWriter, r *http.Request, name string) {
    var urls []string
    //status_codes is populated by callUris(), so let it return the slice with values
    results, status_codes := callUrls(urls)
    //process() needs status_codes in order to work, so pass the variable explicitely
    process(w, results, status_codes)

}

type Response struct {
    status int
    url    string
    body   string
}

func callUrls(urls []string) []*Response {
    ch := make(chan *Response, len(urls))
    //In order to avoid race condition, let's use a channel
    statusChan := make(chan string, len(urls))
    for _, url := range urls {
        go func(url string) {
            //http post on url,
            //base on status code of url call, add to status code
            //some thing like

            req, err := http.NewRequest("POST", url, bytes.NewBuffer(somePostData))
            req.Header.Set("Content-Type", "application/json")
            req.Close = true

            client := &http.Client{
                Timeout: time.Duration(time.Duration(100) * time.Second),
            }

            response, err := client.Do(req)

            if err != nil {
                statusChan <- "200"
                //do other thing with the response received
            } else {
                statusChan <- "500"

            }

            // return to channel accordingly
            ch <- &Response{200, "url", "response body"}

        }(url)
    }
    var results []*Response
    var status_codes []string
    for !doneRes || !doneStatus { //continue until both slices are filled with values
        select {
        case r := <-ch:
            results = append(results, r)
            if len(results) == len(urls) {
                //Done
                close(ch)      //Not really needed here
                doneRes = true //we are done with results, set the corresponding flag

            }
        case status := <-statusChan:
            status_codes = append(status_codes, status)
            if len(status_codes) == len(urls) {
                //Done
                close(statusChan) //Not really needed here
                doneStatus = true //we are done with statusChan, set the corresponding flag
            }
        }

    }
    return results, status_codes
}

func process(w http.ResponseWriter, results []*Response, status_codes []string) {
    fmt.Println("status", status_codes)
}

【讨论】:

  • 我确实尝试过 mutex.lock 和延迟解锁地图功能,但经过一些请求,我看到进程卡在点可能是因为未调用解锁,无论如何。因此,在进行上述更改时,我必须传递更多参数才能写入我可能需要的其他变量?如果需要从请求正文中读取少量值,那也应该作为参数传递,这样其他请求就不会覆盖这些值?
  • 正确。全局变量绝对不是要走的路。如果一个函数需要更多数据来执行其工作,那么只需将这些数据作为显式参数传递。您可以定义一个新的type struct 以便以简洁的方式传递它们。
  • 使用具有 8-9 个键/值的结构会使应用程序消耗更多内存,是否有任何优化内存的方法?响应结构也可能导致内存问题,因为每个请求至少有 10 个 url并且应用程序必须每秒处理数千个请求。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-19
  • 1970-01-01
  • 2019-01-15
  • 1970-01-01
  • 2011-09-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多