douba3378 2019-07-04 14:17
浏览 163

常规:Web服务器中的共享全局变量

I have go web server running on port and handling post request which internally calls different url to fetch response using goroutine and proceed.

I have divided the whole flow to different method. Draft of the code.

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
}

The above code does:

  1. Variable declare globally, Initialized in prepare function.
  2. append value in go routine callUrls function
  3. read those variable in process function

Now should I pass those variable declared globally to each function call to make them local as it won't be shared then?(I would hate to do this.)

Or is there any other approach to achieve the same thing without adding more argument to function being called.

As I will have few other string and int value as well that will be used across the program and in go routine function as well.

What will be the correct way of making them thread safe and only 5 codes for each request coming on port simultaneously.

  • 写回答

1条回答 默认 最新

  • douyinyi7766 2019-07-04 15:22
    关注

    Don't use global variables, be explicit instead and use function arguments. Moreover, you have a race condition on status_codes because it is accessed by multiple goroutines without any mutex lock.

    Take a look at my fix below.

    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)
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥15 在获取boss直聘的聊天的时候只能获取到前40条聊天数据
  • ¥20 关于URL获取的参数,无法执行二选一查询
  • ¥15 液位控制,当液位超过高限时常开触点59闭合,直到液位低于低限时,断开
  • ¥15 marlin编译错误,如何解决?
  • ¥15 有偿四位数,节约算法和扫描算法
  • ¥15 VUE项目怎么运行,系统打不开
  • ¥50 pointpillars等目标检测算法怎么融合注意力机制
  • ¥20 Vs code Mac系统 PHP Debug调试环境配置
  • ¥60 大一项目课,微信小程序
  • ¥15 求视频摘要youtube和ovp数据集