从goroutine获取价值并取消另一个goroutine

时间:2016-09-02 13:08:13

标签: go concurrency goroutine

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

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

简化为:

func A(){
    go funcB(){

    }()

    go funcC(){

    }()

    data := <-channel // 
}

现在收到数据后,funcAfuncB必须取消,无论他们在做什么(我不再关心他们的输出,他们只是在浪费CPU)

最有效的方法是什么? 可以只使用频道吗?

2 个答案:

答案 0 :(得分:6)

context package为此目的提供取消,超时和截止日期上下文。在这里您可以看到取消示例,我们等待较慢的goroutine打印已取消的消息:

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

答案 1 :(得分:2)

根据您的实际使用情况,您有一些选项:

1-使用两个goroutines:

这需要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-在此示例中,您只需启动一个goroutine 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 goroutine中启动A goroutine,它会检测哪个输入已准备就绪,例如ESREDIS,然后相应地启动BC goroutine:

尝试此模拟样本(The Go Playground):
AfterFunc仅用于模拟,在实际代码中你不需要它,它模拟一个输入的随机时序。

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)
)

运行1的输出:

REDIS ready
102
运行2的

输出:

ES ready
101