2016-09-02 37 views
2

我有一个案例,我从两个不同的位置(ES和REDIS)读取数据,我需要从最快的来源读取一个值,因此我正在发射2个goroutines,一个从ES获取数据,另一个从REDIS获取数据。从goroutine获取价值并取消另一个goroutine

一旦从其中一个goroutine获取了数据,另一个goroutine必须完全取消而不会浪费CPU。

简化:

func A(){ 
    go funcB(){ 

    }() 

    go funcC(){ 

    }() 

    data := <-channel // 
} 

现在一旦数据被接收,funcAfuncB必须被取消,不管他们在做什么(我不关心他们的输出了,他们只是在浪费CPU)

什么是最有效的方法呢? 只能使用频道来完成吗?

+0

没有看到代码为每个请求,这更可能是他们没有使用任何CPU ,并正在网络上等待。你打算如何取消这些? – JimB

+0

@JimB没有想法的哥们,这就是为什么我的问题在那里,介意如果我添加您的评论的问题?因为它描述了我的问题的一部分...... –

+0

这更多的是一个修辞问题,因为实际上这样做可能并不可行,需要甚至更有效率。这也取决于服务器(我不认为其中任何一个提供API来取消正在进行的请求),并且客户端库(有一个强制连接关闭的有用钩子)。 – JimB

回答

6

context package提供取消,超时和期限上下文用于此目的。在这里,你可以看到一个取消例子,我们等待较慢够程打印取消的消息:

ctx, cancel := context.WithCancel(context.Background()) 

// buffer the channel for extra results returned before cancelation 
data := make(chan string, 2) 

var wg sync.WaitGroup 
wg.Add(1) 
go func() { 
    defer wg.Done() 
    select { 
    case <-time.After(100 * time.Millisecond): 
     data <- "A complete" 
    case <-ctx.Done(): 
     fmt.Println("A cancelled") 
    } 
}() 

wg.Add(1) 
go func() { 
    defer wg.Done() 
    select { 
    case <-time.After(200 * time.Millisecond): 
     data <- "B complete" 
    case <-ctx.Done(): 
     fmt.Println("B cancelled") 
    } 
}() 

resp := <-data 
cancel() 
fmt.Println(resp) 
wg.Wait() 

https://play.golang.org/p/vAhksjKozW

+0

如果在数据< - “B完成”之前有一个time.sleep(1分钟/秒),并且在数据< - “B完成”之后有println(“pased”)会怎么样? –

+0

@RonanDejhero:我不确定你意思。你可以尝试一下:https://play.golang.org/p/CUCgYeVHO6。这属于“取消”远程请求意味着什么。通常会通过合理的超时设置客户端,以便未完成的请求自行处理。这也取决于客户端代码的外观,以及您如何处理数据。 – JimB

+0

我在这个例子中发现的唯一耗时的代码是“time.After(XXX):”不知道怎么可以用“连接到ES,做一些关于值和返回值的逻辑”来代替 –

2

你有一些选项视乎你的实际使用情况:

1-使用两个够程:

这需要sync/Lock
试试这个模拟样品(The Go Playground):

package main 

import (
    "fmt" 
    "math/rand" 
    "sync" 
    "time" 
) 

func main() { 
    rand.Seed(time.Now().Unix()) 
    time.AfterFunc(time.Duration(rand.Intn(1000))*time.Millisecond, func() { ES <- 101 }) 
    time.AfterFunc(time.Duration(rand.Intn(1000))*time.Millisecond, func() { REDIS <- 102 }) 

    go B() 
    go C() 

    data := <-channel 

    fmt.Println(data) 

} 

func B() { 
    check := true 
    data := 0 
    for { 
     select { 
     case <-quit: 
      return 
     case data = <-ES: // receive data 
     } 
     if check { 
      mx.Lock() 
      //defer mx.Unlock() 
      if mx.done { 
       mx.Unlock() 
       return 
      } 
      check = false 
      close(quit) 
      mx.done = true 
      mx.Unlock() 
     } 
     fmt.Println("ES ready") 
     channel <- data 
    } 
} 

func C() { 
    check := true 
    data := 0 
    for { 
     select { 
     case <-quit: 
      return 
     case data = <-REDIS: // receive data 
     } 
     if check { 
      mx.Lock() 
      //defer mx.Unlock() 
      if mx.done { 
       mx.Unlock() 
       return 
      } 
      check = false 
      close(quit) 
      mx.done = true 
      mx.Unlock() 
     } 
     fmt.Println("REDIS ready") 
     channel <- data 
    } 
} 

var (
    channel = make(chan int) 
    ES  = make(chan int) 
    REDIS = make(chan int) 
    quit = make(chan struct{}) 
    mx  lockdown 
) 

type lockdown struct { 
    sync.Mutex 
    done bool 
} 

2-在此示例中,您只需启动一个常规BC
看到这样的伪代码:

func main() { 
    go A() 
    data := <-channel 
    fmt.Println(data) 
} 

func A() { 
    for{ 
     if ES ready 
      go B(data) 
      return 
     if REDIS ready 
      go C(data) 
      return 
    } 
} 

你可能开始A够程,在A的goroutine它可以检测输入就绪如ESREDIS,然后开始BC够程相应:

试试这个模拟样品(The Go Playground):
AfterFunc仅仅是模拟,真正的代码,你不需要它,它模拟了随机时间为一个输入。从试验1

输出
package main 

import (
    "fmt" 
    "math/rand" 
    "time" 
) 

func main() { 
    rand.Seed(time.Now().Unix()) 
    time.AfterFunc(time.Duration(rand.Intn(1000))*time.Millisecond, func() { ES <- 101 }) 
    time.AfterFunc(time.Duration(rand.Intn(1000))*time.Millisecond, func() { REDIS <- 102 }) 
    go A() 

    data := <-channel 

    fmt.Println(data) 

} 

func A() { 
    select { 
    case data := <-ES: 
     go B(data) 
     return 
    case data := <-REDIS: 
     go C(data) 
     return 
    } 
} 

func B(data int) { 
    for { 
     fmt.Println("ES ready") 
     channel <- data 
     data = <-ES 
    } 
} 
func C(data int) { 
    for { 
     fmt.Println("REDIS ready") 
     channel <- data 
     data = <-REDIS 
    } 
} 

var (
    channel = make(chan int) 
    ES  = make(chan int) 
    REDIS = make(chan int) 
) 

REDIS ready 
102 

从运行2输出:

ES ready 
101 

+0

我在哪里可以停止“REDIS < - 102”的过程?一旦ES返回了? –

+0

@Amd:选项1确实启动了独立的goroutines,你只是在'AfterFunc'后面隐藏'go'调用。 – JimB

+0

@JimB:'AfterFunc'仅用于模拟,在实际代码中您不需要它,它模拟一个输入的随机时间。我会更加澄清这一点。 –