首頁  >  文章  >  後端開發  >  為什麼這個程式掛起?

為什麼這個程式掛起?

PHPz
PHPz轉載
2024-02-14 15:06:081036瀏覽

為什麼這個程式掛起?

php小編西瓜在程式設計過程中,常會遇到程式掛起的問題。程式掛起是指程式在執行過程中突然停止回應,且沒有任何錯誤提示。這種情況常常讓人感到困惑,不知道出了什麼問題。究竟為什麼這個程式掛起?在本文中,我們將探討一些常見的程式掛起原因,並提供解決方案來幫助解決這個問題。無論你是初學者還是有經驗的開發者,相信這些內容都能對你有幫助。

問題內容

我有在 go 中的通道之間進行通訊的程式碼。它似乎完成了所需的操作,但最後掛起。我正在嘗試診斷它為何掛起。

程式碼使用 httpbin.org 取得隨機 uuid,然後將其發布,同時遵守我透過信號量通道和速率通道建立的並發和速率限制。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "sync"
    "time"
)

type HttpBinGetRequest struct {
    url string
}

type HttpBinGetResponse struct {
    Uuid       string `json:"uuid"`
    StatusCode int
}

type HttpBinPostRequest struct {
    url  string
    uuid string // Item to post to API
}

type HttpBinPostResponse struct {
    Data       string `json:"data"`
    StatusCode int
}

func main() {

    // Prepare GET requests for n requests
    var requests []*HttpBinGetRequest
    for i := 0; i < 10; i++ {
        uri := "https://httpbin.org/uuid"
        request := &HttpBinGetRequest{
            url: uri,
        }
        requests = append(requests, request)
    }

    // Create semaphore and rate limit for the GET endpoint
    getSemaphore := make(chan struct{}, 10)
    getRate := make(chan struct{}, 10)
    defer close(getRate)
    defer close(getSemaphore)
    for i := 0; i < cap(getRate); i++ {
        getRate <- struct{}{}
    }

    go func() {
        // ticker corresponding to 1/nth of a second
        // where n = rate limit
        // basically (1000 / rps) * time.Millisecond
        ticker := time.NewTicker(100 * time.Millisecond)
        defer ticker.Stop()
        for range ticker.C {
            _, ok := <-getRate
            if !ok {
                return
            }
        }
    }()

    // Send our GET requests to obtain a random UUID
    respChan := make(chan HttpBinGetResponse)
    var wg sync.WaitGroup
    for _, request := range requests {
        wg.Add(1)
        // cnt := c
        // Go func to make request and receive the response
        go func(r *HttpBinGetRequest) {
            defer wg.Done()

            // Check the rate limiter and block if it is empty
            getRate <- struct{}{}
            // fmt.Printf("Request #%d at: %s\n", cnt, time.Now().UTC().Format("2006-01-02T15:04:05.000Z07:00"))
            resp, _ := get(r, getSemaphore)

            fmt.Printf("%+v\n", resp)
            // Place our response into the channel
            respChan <- *resp
            // fmt.Printf("%+v,%s\n", resp, time.Now().UTC().Format("2006-01-02T15:04:05.000Z07:00"))
        }(request)
    }

    // Set up for POST requests 10/s
    postSemaphore := make(chan struct{}, 10)
    postRate := make(chan struct{}, 10)
    defer close(postRate)
    defer close(postSemaphore)
    for i := 0; i < cap(postRate); i++ {
        postRate <- struct{}{}
    }

    go func() {
        // ticker corresponding to 1/nth of a second
        // where n = rate limit
        // basically (1000 / rps) * time.Millisecond
        ticker := time.NewTicker(100 * time.Millisecond)
        defer ticker.Stop()
        for range ticker.C {
            _, ok := <-postRate
            if !ok {
                return
            }
        }
    }()

    // Read responses as they become available
    for ele := range respChan {
        postReq := &HttpBinPostRequest{
            url:  "https://httpbin.org/post",
            uuid: ele.Uuid,
        }
        go func(r *HttpBinPostRequest) {
            postRate <- struct{}{}
            postResp, err := post(r, postSemaphore)
            if err != nil {
                fmt.Println(err)
            }
            fmt.Printf("%+v\n", postResp)
        }(postReq)

    }
    wg.Wait()
    close(respChan)
}

func get(hbgr *HttpBinGetRequest, sem chan struct{}) (*HttpBinGetResponse, error) {

    // Add a token to the semaphore
    sem <- struct{}{}

    // Remove token when function is complete
    defer func() { <-sem }()
    httpResp := &HttpBinGetResponse{}
    client := &http.Client{}
    req, err := http.NewRequest("GET", hbgr.url, nil)
    if err != nil {
        fmt.Println("error making request")
        return httpResp, err
    }

    req.Header = http.Header{
        "accept": {"application/json"},
    }

    resp, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
        fmt.Println("error getting response")
        return httpResp, err
    }

    // Read Response
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("error reading response body")
        return httpResp, err
    }
    json.Unmarshal(body, &httpResp)
    httpResp.StatusCode = resp.StatusCode
    return httpResp, nil
}

// Method to post data to httpbin
func post(hbr *HttpBinPostRequest, sem chan struct{}) (*HttpBinPostResponse, error) {

    // Add a token to the semaphore
    sem <- struct{}{}
    defer func() { <-sem }()
    httpResp := &HttpBinPostResponse{}
    client := &http.Client{}
    req, err := http.NewRequest("POST", hbr.url, bytes.NewBuffer([]byte(hbr.uuid)))
    if err != nil {
        fmt.Println("error making request")
        return httpResp, err
    }

    req.Header = http.Header{
        "accept": {"application/json"},
    }

    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("error getting response")
        return httpResp, err
    }

    // Read Response
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("error reading response body")
        return httpResp, err
    }
    json.Unmarshal(body, &httpResp)
    httpResp.StatusCode = resp.StatusCode
    return httpResp, nil
}

解決方法

您正在透過 range 語句從程式碼末尾的 respchan 讀取內容。在通道關閉之前,此程式碼不會退出 - 這發生在該程式碼區塊之後。

for ele := range respchan {
    // ...
}


wg.wait()        
close(respchan)

所以程式永遠不會退出 - 因為所有這些邏輯都在同一個 goroutine 中。

要修復並確保在程式退出之前處理所有記錄,請將通道讀取程式碼保留在主 goroutine 中,並將等待/關閉邏輯放入其自己的 goroutine 中:

go func() {
    wg.wait()        // wait for workers to finish ...
    close(respchan)  // ... now signal the main goroutine we're done
}()

for ele := range respchan {
    // ...
}

編輯以等待最終range 循環中的任何子goroutine - 可能有一種更簡潔的方法來僅使用一個等待群組,但一個快速修復可能是:

var swg sync.WaitGroup

go func() {
    wg.Wait()        // wait for workers to finish ...
    swg.Wait()       // ... and sub-tasks
    close(respChan)  // ... now signal the main goroutine we're done
}()

for ele := range respChan {
    // ...
    swg.Add(1)
    go func() {
        defer swg.Done()
        // ...
    }()
}

以上是為什麼這個程式掛起?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:stackoverflow.com。如有侵權,請聯絡admin@php.cn刪除