Go concurrency visual explanation-Select statement

Yesterday, I published an article that visually explained how channels in Golang work. If you still have difficulty understanding channels, it's best to check out that article before reading this article. As a quick refresher: Partier, Candier, and Stringer run a coffee shop. Partier assists in receiving orders from customers and then passes those orders to the kitchen, where Candier and Stringer make the coffee.

49892cf1f41bacfb46d4aadadf9b88b0.png
1*SuZghSKVBqKMuv7E25hWPw.png

Gophers Cafe

In this article, I will visually explain  select statement, another powerful tool for handling concurrency in Go applications. Gophers and their imaginary cafes will still be my companions, but this time, let's focus on the Partier and ordering parts.

Scenes

Gophers Cafe is aware that more and more customers want to order coffee through food delivery apps. So, in addition to ordering on-site, they also opted for a food delivery app. queue Partier monitors orders from these two channels at the same time and forwards these orders to Candier and Stringer through another channel  .

select {
case order := <-appOrders:
    queue <- order
case order := <-inShopOrders:
    queue <- order
}

When any channel receives an order, the Partier forwards it to  queue the channel.

a30643d7faa8caec4ff3c0f2a954ba0e.png
 
bb2c1d6a145925553262093510ac42d5.png
 

If there are orders on both channels, one of them will be selected. In a real cafe,  inShopOrders orders from . However, in a Go application we cannot guarantee which order will be selected. Also note that select each execution of the statement will only select one order, Partier will not select one order and then another. Nonetheless, in many applications select the statements are usually  for inside a loop so that orders left in the previous iteration have a chance of being picked in the next iteration.

for {
    select {
    case order := <-appOrders:
        queue <- order
    case order := <-inShopOrders:
        queue <- order
    }
}

However, if both channels have orders, they will compete fairly again.

04875e5751783e5afef1dfaec1df8735.png
 

Default branch

During off-peak hours, there are not many orders and Partier spends a lot of time waiting. He figured he could use his time more productively by doing other things, such as cleaning his desk. This can be  default achieved by.

for {
    select {
    case order := <-appOrders:
        log.Println("There is an order coming from appOrders channel")
        queue <- order
    case order := <-inShopOrders:
        log.Println("There is an order coming from inShopOrders channel")
        queue <- order
    default:
        log.Println("There is no order on both channels, I will do cleaning instead")
        doCleaning()
    }
}

time.After()

Typically used time.After(duration) with  select to prevent waiting forever. Instead of  default executing immediately when no channel is available, time.After(duration) create a normal one  <-chan Time, wait for  duration it to elapse, and then send the current time on the returned channel. This channel  select is treated like other channels in the statement. As you can see, select the channels in the statement can be of different types.

shouldClose := false
closeHourCh := time.After(8 * time.Hour)


for !shouldClose {
    select {
    case order := <-appOrders:
        log.Println("There is an order coming from appOrders channel")
        queue <- order
    case order := <-inShopOrders:
        log.Println("There is an order coming from inShopOrders channel")
        queue <- order
    case now := <-closeHourCh:
        log.Printf("It is %v now, the shop is closing\n", now)
        shouldClose = true
    default:
        log.Println("There is no order on both channels, I will go cleaning instead")
        doCleaning()
    }
}


log.Println("Shop is closed, I'm going home now. Bye!")

This technique is very common when dealing with remote API calls because we cannot guarantee when or if the remote server will return. With that  context, we usually don't need to do this.

responseChannel := make(chan interface{})
timer := time.NewTimer(timeout)


select {
case resp := <-responseChannel:
    log.Println("Processing response")
    processResponse(resp)
    timer.Stop()
case <-timer.C:
    log.Println("Time out, giving up")
}

Sample code

Let's end this article with a complete fictional cafe code. One more thing to note here, selecting from a closed channel will always return immediately. So if you think it's necessary, use the "comma ok" idiom. Hands-on coding is the best way to learn programming. So if you're  select not too familiar with it, I recommend copying it on your IDE and trying to modify this code.

Happy coding!

package main


import (
    "fmt"
    "log"
    "time"
)


func main() {
    appOrders := make(chan order, 3)
    inShopOrders := make(chan order, 3)
    queue := make(chan order, 3)


    go func() {
        for i := 0; i < 6; i++ {
            appOrders <- order(100 + i)
            time.Sleep(10 * time.Second)
        }
        close(appOrders)
    }()


    go func() {
        for i := 0; i < 4; i++ {
            inShopOrders <- order(200 + i)
            time.Sleep(15 * time.Second)
        }
        close(inShopOrders)
    }()


    go partier(appOrders, inShopOrders, queue)


    for o := range queue {
        log.Printf("Served %s\n", o)
    }


    log.Println("Done!")
}


func partier(appOrders <-chan order, inShopOrders <-chan order, queue chan<- order) {
    shouldClose := false
    closeTimeCh := time.After(1 * time.Minute)


    for !shouldClose {
        select {
        case ord, ok := <-appOrders:
            if ok {
                log.Printf("There is %s coming from appOrders channel\n", ord)
                queue <- ord
            }
        case ord, ok := <-inShopOrders:
            if ok {
                log.Printf("There is %s coming from inShopOrders channel\n", ord)
                queue <- ord
            }
        case now := <-closeTimeCh:
            log.Printf("It is %v now, the shop is closing\n", now)
            shouldClose = true
        default:
            log.Println("There is no order on both channels, I will go cleaning instead")
            doCleaning()
        }
    }


    close(queue)
    log.Println("Shop is closed, I'm going home now. Bye!")
}


func doCleaning() {
    time.Sleep(5 * time.Second)
    log.Println("Partier: Cleaning done")
}


type order int


func (o order) String() string {
    return fmt.Sprintf("order-%02d", o)
}

Thank you for reading until the end of the article. Please consider following the author~

Guess you like

Origin blog.csdn.net/weixin_37604985/article/details/132506180