Go:通过渠道传递功能

时间:2016-01-07 06:32:34

标签: go concurrency rate-limiting

我试图通过将它们放入队列以便稍后访问来对我调用的函数进行速率限制。下面我有一些我创建的请求,requestHandler函数以一定的速率处理每个请求。

我希望它接受具有不同类型参数的各种函数,因此接口{}类型。

我如何能够通过频道传递功能并成功调用它们?

type request struct {
    function interface{}
    channel  chan interface{}
}

var requestQueue []request

func pushQueue(f interface{}, ch chan interface{}) {
    req := request{
        f,
        ch,
    }

    //push
    requestQueue = append(requestQueue, req)
}

func requestHandler() {
    for {
        if len(requestQueue) > 0 {
            //pop
            req := requestQueue[len(requestQueue)-1]
            requestQueue = requestQueue[:len(requestQueue)-1]

            req.channel <- req.function
        }

        <-time.After(1200 * time.Millisecond)
    }
}

以下是我正在尝试实现的示例(GetLeagueEntries(string,string)和GetSummonerName(int,int)是函数):

ch := make(chan interface{})
    pushQueue(l.GetLeagueEntries, ch)
    pushQueue(l.GetSummonerName, ch)

    leagues, _ := <-ch(string1, string2)
    summoners, _ := <-ch(int1, int2)

3 个答案:

答案 0 :(得分:2)

首先,我会把它写成:

leagues := server.GetLeagueEntries()
summoners := server.GetSummoners()

然后,将速率限制放入服务器。使用其中一个速率限制库。

但是,可以使用接口来统一请求,并使用func类型来允许闭包(如在http.HandleFunc中):

type Command interface {
    Execute(server *Server)
}

type CommandFunc func(server *Server)
func (fn CommandFunc) Execute(server *Server) { fn(server) }

type GetLeagueEntries struct { Leagues []League }

func (entries *GetLeagueEntries) Execute(server *Server) {
    // ...
}

func GetSummonerName(id int, result *string) CommandFunc {
    return CommandFunc(func(server *Server){
        *result = "hello"
    })
}

get := GetLeagueEnties{}
requests <- &get

requests <- CommandFunc(func(server *Server){
    // ... handle struff here
})

当然,这需要一些同步。

答案 1 :(得分:0)

我原本以为使用某种信号量或工作池更容易。这样,你可以做任何事情的工人数量有限。也可能有多个工作池。

您是否需要这些调用中的任何一个是并发/异步的?如果没有,他们可以被调用,以便你可以有可配置的睡眠(一个讨厌的黑客头脑)。

尝试工作池或信号量而不是功能。

答案 2 :(得分:0)

好的,这是codez:https://play.golang.org/p/XZvb_4BaJF

请注意,它并不完美。您有一个每秒执行一次的队列。如果队列为空并且添加了新项目,则新项目可以在执行之前等待将近一秒钟。

但是这应该让你非常接近你所需要的东西:)

此代码可分为3个部分:

  1. 速率受限的队列执行器,我称之为服务器(我在命名事物上很糟糕) - 服务器对这些功能一无所知。它所做的只是启动一个永无止境的goroutine,它会每秒一次弹出队列中最旧的函数并调用它。我上面谈到的问题是在BTW代码的这一部分,如果你愿意,我可以帮你修复它。
  2. 按钮单击功能 - 这将显示每个按钮单击如何使用服务器调用3个差异功能(显然可以进行更多/更少的函数调用)并确保它们彼此相隔1秒。你甚至可以为任何一个函数添加超时(伪造延迟),它们仍会被分别调用1秒。这是你需要频道的唯一地方,因为你想尽可能快地进行所有函数调用(如果第一个函数需要5秒,你只想等待1秒钟来调用第二个函数)然后等待它们完成所以你需要知道他们什么时候完成。
  3. 按钮单击模拟(主要功能) - 这只显示3个按钮点击将按预期工作。你也可以把它们放在一个goroutine来模拟3个用户同时点击按钮,它仍然可以工作。

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    const (
        requestFreq = time.Second
    )
    
    type (
        // A single request
        request func()
    
        // The server that will hold a queue of requests and make them once a requestFreq
        server struct {
            // This will tick once per requestFreq
            ticker     *time.Ticker
    
            requests []request
            // Mutex for working with the request slice
            sync.RWMutex
        }
    )
    
    var (
        createServerOnce sync.Once
        s *server
    )
    
    func main() {
        // Multiple button clicks:
        ButtonClick()
        ButtonClick()
        ButtonClick()
    
        fmt.Println("Done!")
    }
    
    
    
    
    
    
    // BUTTON LOGIC:
    
    // Calls 3 functions and returns 3 diff values.
    // Each function is called at least 1 second appart.
    func ButtonClick() (val1 int, val2 string, val3 bool) {
        iCh := make(chan int)
        sCh := make(chan string)
        bCh := make(chan bool)
    
        go func(){
            Server().AppendRequest(func() {
                t := time.Now()
                fmt.Println("Calling func1 (time: " + t.Format("15:04:05") + ")")
                // do some stuff
                iCh <- 1
            })
        }()
        go func(){
            Server().AppendRequest(func() {
                t := time.Now()
                fmt.Println("Calling func2 (time: " + t.Format("15:04:05") + ")")
                // do some stuff
                sCh <- "Yo"
            })
        }()
        go func(){
            Server().AppendRequest(func() {
                t := time.Now()
                fmt.Println("Calling func3 (time: " + t.Format("15:04:05") + ")")
                // do some stuff
                bCh <- true
            })
        }()
    
        // Wait for all 3 calls to come back
        for count := 0; count < 3; count++ {
            select {
            case val1 = <-iCh:
            case val2 = <-sCh:
            case val3 = <-bCh:
            }
        }
    
        return
    }
    
    
    
    
    
    // SERVER LOGIC
    
    // Factory function that will only create a single server
    func Server() *server {
        // Only one server for the entire application
        createServerOnce.Do(func() {
            s = &server{ticker: time.NewTicker(requestFreq), requests: []request{}}
    
            // Start a thread to make requests.
            go s.makeRequests()
        })
        return s
    }
    func (s *server) makeRequests() {
        if s == nil || s.ticker == nil {
            return
        }
    
        // This will keep going once per each requestFreq
        for _ = range s.ticker.C {
    
            var r request
    
            // You can't just access s.requests because you are in a goroutine
            // here while someone could be adding new requests outside of the 
            // goroutine so you have to use locks.
            s.Lock()
            if len(s.requests) > 0 {
                // We have a lock here, which blocks all other operations 
                // so just shift the first request out, save it and give 
                // the lock back before doing any work.
                r = s.requests[0]
                s.requests = s.requests[1:]
            }
            s.Unlock()
    
            if r != nil {
                // make the request!
                r()
            }
        }
    }
    func (s *server) AppendRequest(r request) {
        if s == nil {
            return
        }
        s.Lock()
        s.requests = append(s.requests, r)
        s.Unlock()
    }