Go Routine: общая глобальная переменная на веб-сервере - PullRequest
0 голосов
/ 04 июля 2019

У меня есть веб-сервер, работающий на порте и обрабатывающий почтовый запрос, который внутренне вызывает другой 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. Переменная объявляет глобально, инициализируется в функции prepare.
  2. добавляет значение в функцию go callUrls.
  3. читать эти переменные в функции процесса

Теперь я должен передать эти переменные, объявленные глобально, каждому вызову функции, чтобы сделать их локальными, поскольку тогда они не будут использоваться совместно (я бы не хотелчтобы сделать это.)

Или есть какой-то другой подход для достижения того же самого, не добавляя больше аргумента к вызываемой функции.

Поскольку у меня будет немного других строковых и целых значений, чтобудет использоваться во всей программе и в обычной функции go.

Какой будет правильный способ сделать их поточно-ориентированными, и только 5 кодов для каждого запроса, поступающего на порт одновременно.

1 Ответ

0 голосов
/ 04 июля 2019

Не используйте глобальные переменные, вместо этого используйте явные аргументы функции.Более того, у вас есть состояние гонки на status_codes, потому что к нему обращаются множественные программы без блокировки мьютекса.

Взгляните на мое исправление ниже.

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)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...