前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >go 并发模式

go 并发模式

作者头像
solate
发布于 2022-10-28 09:14:07
发布于 2022-10-28 09:14:07
57300
代码可运行
举报
文章被收录于专栏:solate 杂货铺solate 杂货铺
运行总次数:0
代码可运行

1. 函数返回channel

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main
 
import (
	"fmt"
	"math/rand"
	"time"
)
 
func main() {
	c := boring("boring!") // Function returning a channel.
	for i := 0; i < 5; i++ {
		fmt.Printf("You say: %q\n", <-c)
	}
	fmt.Println("You're boring; I'm leaving.")
}
 
func boring(msg string) <-chan string { // Returns receive-only channel of strings. 
	c := make(chan string)
	go func() { // We launch the goroutine from inside the function.
		for i := 0; ; i++ {
			c <- fmt.Sprintf("%s %d", msg, i)
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
		}
	}()
	return c // Return the channel to the caller.
}

boring返回一个channel,不断往里写数据。main调用,并从channel中获取数据,结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
You say: "boring! 0"
You say: "boring! 1"
You say: "boring! 2"
You say: "boring! 3"
You say: "boring! 4"
You're boring; I'm leaving.

1.1 通道做句柄

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main
 
import (
	"fmt"
	"math/rand"
	"time"
)
 
func main() {
	joe := boring("Joe")
	ann := boring("Ann")
	for i := 0; i < 5; i++ {
		fmt.Println(<-joe)
		fmt.Println(<-ann) // ann will block if joe is not ready to give a value
	}
	fmt.Println("You're both boring; I'm leaving.")
}
 
func boring(msg string) <-chan string { // Returns receive-only channel of strings.
	c := make(chan string)
	go func() { // We launch the goroutine from inside the function.
		for i := 0; ; i++ {
			c <- fmt.Sprintf("%s: %d", msg, i)
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
		}
	}()
	return c // Return the channel to the caller.
}

返回多个boring服务,channel做服务的句柄(也就是唯一标识)。返回channel时,通道没有数据会阻塞,按顺序来即可保证输出顺序。 结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Joe: 0
Ann: 0
Joe: 1
Ann: 1
Joe: 2
Ann: 2
Joe: 3
Ann: 3
Joe: 4
Ann: 4
You're both boring; I'm leaving.

但是当joe 阻塞的时候,就会阻塞ann,程序会阻塞在Joe读取这里。

扇入(fan in): 多个channel 并入一个

多个channel 并入一个channel 输出, 不考虑顺序,随机输出

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

func main() {
	c := fanIn(boring("Joe"), boring("Ann"))
	for i := 0; i < 10; i++ {
		fmt.Println(<-c) // HL
	}
	fmt.Println("You're both boring; I'm leaving.")
}

func boring(msg string) <-chan string { // Returns receive-only channel of strings.
	c := make(chan string)
	go func() { // We launch the goroutine from inside the function.
		for i := 0; ; i++ {
			c <- fmt.Sprintf("%s: %d", msg, i)
			time.Sleep(time.Duration(rand.Intn(2e3)) * time.Millisecond)
		}
	}()
	return c // Return the channel to the caller.
}

func fanIn(input1, input2 <-chan string) <-chan string {
	c := make(chan string)
	go func() {
		for {
			c <- <-input1
		}
	}()
	go func() {
		for {
			c <- <-input2
		}
	}()
	return c
}

输出类似如下, 不考虑顺序时可以这样使用,类似开车时的汇入,“前方小心右侧车辆汇入”。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Ann: 0
Joe: 0
Ann: 1
Joe: 1
Ann: 2
Joe: 2
Ann: 3
Ann: 4
Ann: 5
Joe: 3
You're both boring; I'm leaving.

input1、input2和c的关系如下图所示:

阻塞与按序恢复

使用channel 阻塞打印,使得打印按照顺序进行打印.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

type Message struct {
	str string
	wait chan bool
}

func main() {
	c := fanIn(boring("Joe"), boring("Ann"))

	for i := 0; i < 5; i++ {
		msg1 := <-c; fmt.Println(msg1.str)
		msg2 := <-c; fmt.Println(msg2.str)
		msg1.wait <- true // block boring, false is also ok
		msg2.wait <- true
	}

	fmt.Println("You're both boring; I'm leaving.")
}

func boring(msg string) <-chan Message { // Returns receive-only channel of strings.
	c := make(chan Message)

	waitForIt := make(chan bool) // Shared between all messages.

	go func() { // We launch the goroutine from inside the function.
		for i := 0; ; i++ {

			c <- Message{ fmt.Sprintf("%s: %d", msg, i), waitForIt }
			time.Sleep(time.Duration(rand.Intn(2e3)) * time.Millisecond)
			<-waitForIt // to be blocked
		}
	}()
	return c // Return the channel to the caller.
}

func fanIn(inputs ... <-chan Message) <-chan Message {
	c := make(chan Message)
	for i := range inputs {
		input := inputs[i] // New instance of 'input' for each loop.
		go func() { for { c <- <-input } }()
	}
	return c
}

结果如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Joe: 0
Ann: 0
Joe: 1
Ann: 1
Joe: 2
Ann: 2
Joe: 3
Ann: 3
Joe: 4
Ann: 4
You're both boring; I'm leaving.

乱序到达,通过通道形成加锁同步。通道作为锁,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<-waitForIt

会阻塞,直到有数据。

因为没有设置缓冲区,强行将输出同步, 作为知识点了解,实际作用不大。

使用select

与switch语句相比,select有比较多的限制,其中最大的一条限制就是每个case语句后面必须是一个Io操作 。

select 作用主要是配合channel实现IO多路复用, 更多多路复用可以查看下面参考资料

扇入(fan In)模式与Select的结合

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

func main() {
	c := fanIn(boring("Joe"), boring("Ann"))
	for i := 0; i < 10; i++ {
		fmt.Println(<-c)
	}
	fmt.Println("You're both boring1; I'm leaving.")
}

func boring(msg string) <-chan string { // Returns receive-only channel of strings.
	c := make(chan string)
	go func() { // We launch the goroutine from inside the function.
		for i := 0; ; i++ {
			c <- fmt.Sprintf("%s: %d", msg, i)
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
		}
	}()
	return c // Return the channel to the caller.
}

func fanIn(input1, input2 <-chan string) <-chan string {
	c := make(chan string)
	go func() {
		for {
			select {
			case s := <-input1:
				c <- s
			case s := <-input2:
				c <- s
			}
		}
	}()
	return c
}

fanIn不再使用多个goroutine了,而是使用一个goroutine,在其中有无限循环和select。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Ann: 0
Joe: 0
Ann: 1
Joe: 1
Ann: 2
Joe: 2
Ann: 3
Joe: 3
Ann: 4
Joe: 4
You're both boring1; I'm leaving.

模式3:通信超时

单次超时

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

func init() {
	rand.Seed(time.Now().Unix())  // 这里多加一个rand 随机数
}

func main() {
	c := boring("Joe")
	for {
		select {
		case s := <-c:
			fmt.Println(s)
		case <-time.After(1 * time.Second): // if you change it to more than 1.5 seconds it will not block, eg. 5
			fmt.Println("You're too slow.") // it's time to stop last case
			return  // 这里必须是return 不能是break
		}
	}
}

func boring(msg string) <-chan string { // Returns receive-only channel of strings.
	c := make(chan string)
	go func() { // We launch the goroutine from inside the function.
		for i := 0; ; i++ {
			c <- fmt.Sprintf("%s: %d", msg, i)
			time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
		}
	}()
	return c // Return the channel to the caller.
}

因为两个chan都是阻塞的,每次for循环,得到的time.After都是新的1秒,当数字随机的数字大于1秒时,则退出。

有的时候boring服务可能是页面访问,获取资源等服务,我们并不清楚需要多长时间,但是我们有一个时间上限。这个时候可以使用库函数,time.After,到达等待时间后它会返回一个channel,这时我们可以退出程序。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Joe: 0
You're too slow.

在使用select 时候一般我们配合for循环用,如果想跳出for,要用 return进行跳出,不要用break,因为break只会跳出select ,不会跳出for循环

如果在主协程(main)的select 内用runtime.Goexit() 进行退出 会报错: no goroutines (main called runtime.Goexit) - deadlock! ,还是要用return

总体超时

总体超时需要将timeout 定义在for循环之外

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

func main() {
	c := boring("Joe")
	timeout := time.After(5 * time.Second)
	for {
		select {
		case s := <-c:
			fmt.Println(s)
		case <-timeout:
			fmt.Println("You talk too much.")
			return
		}
	}
}

func boring(msg string) <-chan string { // Returns receive-only channel of strings.
	c := make(chan string)
	go func() { // We launch the goroutine from inside the function.
		for i := 0; ; i++ {
			c <- fmt.Sprintf("%s: %d", msg, i)
			time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
		}
	}()
	return c // Return the channel to the caller.
}

我们只定义一个timeout,到达后就退出。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Joe: 0
Joe: 1
Joe: 2
Joe: 3
Joe: 4
Joe: 5
Joe: 6
Joe: 7
Joe: 8
You talk too much.

使用context.WithTimeout来控制超时退出

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	ctx, _ := context.WithTimeout(context.TODO(), 3*time.Second) //定义一个超时的 context

	stop := make(chan struct{})
	go func() {
		time.Sleep(4 * time.Second)
		close(stop)
	}()
	for {
		select {
		case <-stop:
			fmt.Println("Stop ... ")
			return
		case <-ctx.Done():
			fmt.Println("context ... ")
			return
		}
	}

}

结果是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
context ...

周期定时

在循环前定义time.Tick

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

func main() {
	timeout := time.After(3 * time.Second)
	timetrick := time.Tick(time.Second)
	c := make(chan int)
	go func() {
		for {
			c <- 0
			time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
		}
	}()
	for {
		select {
		case <-c:
			fmt.Println("I'm working...")
		case <-timetrick:
			fmt.Println("1 second pass")
		case <-timeout:
			fmt.Println("3 second")
			return
		}
	}
}

结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
I'm working...
I'm working...
I'm working...
I'm working...
I'm working...
I'm working...
1 second pass
I'm working...
I'm working...
I'm working...
1 second pass
I'm working...
I'm working...
I'm working...
I'm working...
I'm working...
I'm working...
3 second

模式4:自定义退出

定义一个quit 来控制退出

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

func main() {
	quit := make(chan bool)
	c := boring("Joe", quit)
	for i := rand.Intn(10); i >= 0; i-- {
		fmt.Println(<-c)
	}
	// modify by lady_killer9
	fmt.Println("You're boring!")
	quit <- true
}

func boring(msg string, quit <-chan bool) <-chan string {
	c := make(chan string)
	go func() {
		for i := 0; ; i++ {
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
			select {
			case c <- fmt.Sprintf("%s: %d", msg, i):
				// do nothing
			case <-quit:
				return
			}
		}
	}()
	return c
}

想退出时,在select的某个return的case对应的channel中写入数据

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Joe: 0
Joe: 1
You're boring!

安全的退出

退出时做清理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

func cleanup() {
	// added by lady_killer9
	fmt.Println("clean up ")
}

func main() {
	quit := make(chan string)
	c := boring("Joe", quit)
	for i := rand.Intn(10); i >= 0; i-- {
		fmt.Println(<-c)
	}
	quit <- "Bye!"
	fmt.Printf("Joe says: %q\n", <-quit)
}

func boring(msg string, quit chan string) <-chan string {
	c := make(chan string)
	go func() {
		for i := 0; ; i++ {
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
			select {
			case c <- fmt.Sprintf("%s: %d", msg, i):
				// do nothing
			case <-quit:
				cleanup()
				quit <- "See you!"
				return
			}
		}
	}()
	return c
}

有的时候,我们收到要关闭的消息后,需要进行文件关闭等清理工作,之后再告诉主程序我们清理完毕,可以退出了,防止内存泄露,文件占用等情况。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Joe: 0
Joe: 1
clean up 
Joe says: "See you!"

goroutine的速度

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main
 
import (
	"fmt"
	"time"
)
 
func f(left, right chan int) {
	left <- 1 + <-right
}
 
func main() {
	const n = 10000
	leftmost := make(chan int)
	right := leftmost
	left := leftmost
	for i := 0; i < n; i++ {
		right = make(chan int)
		go f(left, right)
		left = right
	}
	start := time.Now()
	go func(c chan int) { c <- 1 }(right)
	fmt.Println(<-leftmost)
	fmt.Println(time.Since(start))
}

类似于传话筒游戏,我们不断的右耳朵进,左耳朵出。这里使用了10000个goroutine,结果如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
10001
6.955789ms

注: 博客里是3.5ms左右,可能是我机器还跑了其他东西的原因

大概花了3ms多一点,就完成了10000个goroutine的通信,如果使用Python等其他语言是很难达到的,这就是goroutine,简单,高效。

Google Search

Google搜索是一个很好的例子,我们输入问题,然后Google发给多个后端程序进行搜索,可能是网页,图片,视频等,最后将结果进行一个汇总并返回。接下来进行一个仿造:

Google Search 1.0

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

type Result string

func Google(query string) (results []Result) {
	results = append(results, Web(query))
	results = append(results, Image(query))
	results = append(results, Video(query))
	return
}

var (
	Web   = fakeSearch("web")
	Image = fakeSearch("image")
	Video = fakeSearch("video")
)

type Search func(query string) Result

func fakeSearch(kind string) Search {
	return func(query string) Result {
		time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
		return Result(fmt.Sprintf("%s result for %q\n", kind, query))
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	start := time.Now()
	results := Google("golang")
	elapsed := time.Since(start)
	fmt.Println(results)
	fmt.Println(elapsed)
}

在Google时,只是将结果放入结果队列依次放入会等待上一个结果出来。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[web result for "golang"
 image result for "golang"
 video result for "golang"
]
84.514029ms

等待太浪费时间了,我们可以使用goroutine

Google Search 2.0

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

type Result string
type Search func(query string) Result

var (
	Web   = fakeSearch("web")
	Image = fakeSearch("image")
	Video = fakeSearch("video")
)

func Google(query string) (results []Result) {
	c := make(chan Result)
	go func() { c <- Web(query) }()
	go func() { c <- Image(query) }()
	go func() { c <- Video(query) }()

	for i := 0; i < 3; i++ {
		result := <-c
		results = append(results, result)
	}
	return
}

func fakeSearch(kind string) Search {
	return func(query string) Result {
		time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
		return Result(fmt.Sprintf("%s result for %q\n", kind, query))
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	start := time.Now()
	results := Google("golang")
	elapsed := time.Since(start)
	fmt.Println(results)
	fmt.Println(elapsed)
}

使用goroutine就不必等待上一个结果出来 (使用并发扇入这种方式,将调用方法并发)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[image result for "golang"
 web result for "golang"
 video result for "golang"
]
63.255893ms

Google Search 2.1

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

type Result string
type Search func(query string) Result

var (
	Web   = fakeSearch("web")
	Image = fakeSearch("image")
	Video = fakeSearch("video")
)

func Google(query string) (results []Result) {
	c := make(chan Result)
	go func() { c <- Web(query) }()
	go func() { c <- Image(query) }()
	go func() { c <- Video(query) }()

	timeout := time.After(80 * time.Millisecond)
	for i := 0; i < 3; i++ {
		select {
		case result := <-c:
			results = append(results, result)
		case <-timeout:
			fmt.Println("timed out")
			return
		}
	}
	return
}

func fakeSearch(kind string) Search {
	return func(query string) Result {
		time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
		return Result(fmt.Sprintf("%s result for %q\n", kind, query))
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	start := time.Now()
	results := Google("golang")
	elapsed := time.Since(start)
	fmt.Println(results)
	fmt.Println(elapsed)
}

在Google 2.0的fan In模式的基础上,增加了总体超时模式,超过时不再等待其他结果。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
timed out
[web result for "golang"
 image result for "golang"
]
81.379159ms

我们如何避免丢弃慢速服务器的结果呢?例如,上面的video被丢弃了 答:复制服务器。向多个副本发送请求,并使用第一个响应

Google Search 3.0

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

type Result string
type Search func(query string) Result

func First(query string, replicas ...Search) Result {
	c := make(chan Result)
	searchReplica := func(i int) { c <- replicas[i](query) }
	for i := range replicas {
		go searchReplica(i)
	}
	return <-c
}

func main() {
	rand.Seed(time.Now().UnixNano())
	start := time.Now()
	result := First("golang",
		fakeSearch("replica 1"),
		fakeSearch("replica 2"))
	elapsed := time.Since(start)
	fmt.Println(result)
	fmt.Println(elapsed)
}

func fakeSearch(kind string) Search {
	return func(query string) Result {
		time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
		return Result(fmt.Sprintf("%s result for %q\n", kind, query))
	}
}

对于同一个问题,我们启用多个副本,返回最快的服务器搜索到的结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
replica 1 result for "golang"

1.388248ms

随机时间不同,导致传入最先传入c的被拿到,然后return了

接下来,我们针对web、image、video都启用多个服务器进行搜索

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

type Result string
type Search func(query string) Result

var (
	Web1   = fakeSearch("web1")
	Web2   = fakeSearch("web2")
	Image1 = fakeSearch("image1")
	Image2 = fakeSearch("image2")
	Video1 = fakeSearch("video1")
	Video2 = fakeSearch("video2")
)

func Google(query string) (results []Result) {
	c := make(chan Result)
	go func() { c <- First(query, Web1, Web2) }()
	go func() { c <- First(query, Image1, Image2) }()
	go func() { c <- First(query, Video1, Video2) }()
	timeout := time.After(80 * time.Millisecond)
	for i := 0; i < 3; i++ {
		select {
		case result := <-c:
			results = append(results, result)
		case <-timeout:
			fmt.Println("timed out")
			return
		}
	}
	return
}

func First(query string, replicas ...Search) Result {
	c := make(chan Result)
	searchReplica := func(i int) {
		c <- replicas[i](query)
	}
	for i := range replicas {
		go searchReplica(i)
	}
	return <-c
}

func fakeSearch(kind string) Search {
	return func(query string) Result {
		time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
		return Result(fmt.Sprintf("%s result for %q\n", kind, query))
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	start := time.Now()
	results := Google("golang")
	elapsed := time.Since(start)
	fmt.Println(results)
	fmt.Println(elapsed)
}

通过多个副本,选择最快的一个的方式,基本可以保证每种类型的结果都能在超时时间内完成。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[web2 result for "golang"
 video1 result for "golang"
 image2 result for "golang"
]
37.218453ms

callback模式--作为参数

go开启一个协程,并不知道什么时候会结束,其他语言一般会有callback,在go中,函数可以作为参数实现callback,这里再说一种channel实现callback的思路。

开启的协程,可以在结束后像一个channel中写入值,main routine中读取,即可实现阻塞直到完成。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

func doSomething(event string, ch chan<- bool) {
	time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
	ch <- true
}

func main() {
	ch := make(chan bool)
	go doSomething("event", ch)
	for {
		select {
		case <-ch:
			fmt.Println("finish...")
			return
		}
	}
}

一般是等待第一个协程结束或所有协程结束,如果多个协程的话可以计数。

sync里面有WaitGroup, 可以wait所有协程

缓存模式-- channel带buffer

举个例子,假设厨师做饭一会儿做的快,可能是凉菜,一会儿做的慢,可能是佛跳墙。服务员的端菜速度是一定的,如果菜没有地方放,只能等待服务员拿的话,就会很慢,因为厨师做快了没有地方放(阻塞),做慢了的话服务员要一直等(阻塞)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

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

var food = make(chan string)

func cook(foods []string) {
	for _, f := range foods {
		if f == "凉菜" {
			time.Sleep(100 * time.Millisecond)
			food <- "凉菜"
		} else {
			time.Sleep(800 * time.Millisecond)
			food <- "佛跳墙"
		}
	}
}

func server(finish chan bool) {
	for {
		select {
		case name := <-food:
			time.Sleep(time.Duration(rand.Intn(600)) * time.Millisecond)
			fmt.Println("客官,上菜了:", name)
			finish <- true
		default:
			//fmt.Println("服务员在等待...")
		}
	}
}

func main() {
	foods := []string{"凉菜", "凉菜", "凉菜", "凉菜", "凉菜", "凉菜", "佛跳墙", "凉菜"}
	n := len(foods)
	cnt := 0
	finish := make(chan bool)
	start := time.Now()
	go cook(foods)
	go server(finish)
	for {
		select {
		case <-finish:
			{
				cnt += 1
				if cnt == n {
					fmt.Println(time.Since(start))
					return
				}
			}
		}
	}
}

结果是2秒多

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 佛跳墙
客官,上菜了: 凉菜
2.22571934s

上面代码是没有缓存的,会慢一些,如果把food改为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var food = make(chan string,6)

结果是1.6秒, 比不带buff 阻塞的快了些

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 凉菜
客官,上菜了: 佛跳墙
客官,上菜了: 凉菜
1.666686866s

参考

Go-并发模式总结(扇入模式,超时模式,callback模式等)

Go-并发模式2(Patterns

掌握golang select IO多路复用

这个参考里的源码已经没用kind类型了,其他说明还可以看看。

Go 并发编程|select语句(IO多路复用)

详解Go语言I/O多路复用netpoller模型

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
一种不错的 BFF Microservice GraphQL/REST API 层的开发方式
云原生(Cloud Native)Node JS Express Reactive 微服务模板 (REST/GraphQL) 这个项目提供了完整的基于 Node JS / Typescript 的微服务模板,包括生产部署、监控、调试、日志记录、安全、CI/CD 所需的所有功能。还添加了基于响应性扩展的示例,以演示如何将其用于构建微服务 API 边缘服务(edge-service)、前端的后端(BFF)或将其用作构建任何类型微服务的基础。
为少
2021/05/27
2.4K0
一种不错的 BFF Microservice GraphQL/REST API 层的开发方式
扔掉小红书,国外自由行:Pokémon Go 和 Google Gemini 帮助打造最强旅游 Copilot
在现代旅游时代,传统导游面临着 Pokémon Go 和 Google Gemini 等创新技术的竞争。这些数字伴侣提供 7x24 全天候的可访问性、丰富的知识和个性化体验,改变了我们探索世界的方式。虽然传统指南可能会受到世界知识和可用性的限制,但 Pokémon Go 和 Google Gemini 可以根据个人兴趣无缝访问信息和建议。从发现隐藏的瑰宝到解开文化奥秘,这些技术丰富了旅行体验,为每一次旅程提供见解和陪伴。展望未来,大语言模型和增强现实的整合为沉浸式探索带来了更大的可能性,在不断发展的旅行领域弥合好奇心和理解之间的差距。在旅游大模型这个賽道有可能诞生出千亿美金的 AI 原生公司出来。
深度学习与Python
2024/06/17
1470
扔掉小红书,国外自由行:Pokémon Go 和 Google Gemini 帮助打造最强旅游 Copilot
各类好玩免费API推荐,强烈建议收藏
有些读者刚开始学习编程遇到API或者接口不太明白到底什么意思,没关系,行哥这里帮你百度一下
行哥玩Python
2020/07/31
2.5K0
超硬核 Web 前端学霸笔记,学完就去找工作!
文章和教程 Vue 学习笔记 Node 学习笔记 React 学习笔记 Angular 学习笔记 RequireJS 学习笔记 Webpack 学习笔记 Gulp 学习笔记 Python 学习笔记 Egret 引擎学习笔记 流处理,TCP 和 UDP,WebRTC 和 Blob 学习笔记 博客 前端回忆录 | 前端笔记本 - 一个前端博主记录的心得和总结 Hasnode - Hashnode 是在您的个人域 free 上免费创建开发者博客并通过我们的全球开发者社区与读者联系的最简单方法! 👩‍💻👨‍💻 De
wscats
2022/03/28
1.5K0
.NET周刊【12月第2期 2024-12-08】
https://www.cnblogs.com/sheng_chao/p/18581139
InCerry
2024/12/20
1600
.NET周刊【12月第2期 2024-12-08】
使用identity+jwt保护你的webapi(二)——获取jwt token
上一篇已经介绍了identity在web api中的基本配置,本篇来完成用户的注册,登录,获取jwt token。
xhznl
2021/10/18
1K0
使用identity+jwt保护你的webapi(二)——获取jwt token
WEB开发中40+高质量的免费资源【多图但值得一看】
对开发者来说,资源非常重要,因为在编码的时候可以提高很大程度上提高生产力。通过几个月,我收集到了一些链接,然后我很乐意跟大家分享其中部分。希望它们对你有所帮助。废话少说,这里提供了很棒的免费且最新的资源。
Jimmy_is_jimmy
2020/06/16
9430
WEB开发中40+高质量的免费资源【多图但值得一看】
超百个免费api接口,分享给你「建议收藏」
API(Application Programming Interface,应用程序接口)是一些预先定义的函数,或指软件系统不同组成部分衔接的约定。目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问原码,或理解内部工作机制的细节。
全栈程序员站长
2022/08/25
53.1K0
超百个免费api接口,分享给你「建议收藏」
作为程序员,我都逛了哪些技术网站?(收藏篇)
首先列出一些在线教程网站,这些在线教程网站通常都比较适合入门,可以作为开发学习路上的第一个阶梯,也可以作为工作中的在线文档。
程序员小猿
2021/01/20
1.5K0
作为程序员,我都逛了哪些技术网站?(收藏篇)
数据接口-免费版(股票数据API)「建议收藏」
获取股票数据的源头主要有:数据超市、雅虎、新浪、Google、和讯、搜狐、ChinaStockWebService、东方财富客户端、证券之星、网易财经。
全栈程序员站长
2022/07/23
36.9K0
​调试必备!详解 HTTP 客户端调用 K8S API,建议收藏!
使用 CLI(如 curl)或 GUI(如 postman )HTTP 客户端调用 Kubernetes API 有很多原因。例如,您可能需要对 Kubernetes 对象进行比 kubectl 提供的更细粒度的控制,或者只是想在尝试从代码访问 API 之前进行探索。
我的小碗汤
2023/03/19
11.2K0
​调试必备!详解 HTTP 客户端调用 K8S API,建议收藏!
使用 Cloudflare Worker 免费搭建网址导航网站
GitHub:https://github.com/sleepwood/CF-Worker-Dir/ CloudFlare Worker:https://workers.cloudflare.com/ 演示地址:https://daohang.inkedus.workers.dev/
Inkedus
2020/04/16
5.2K0
使用 Cloudflare Worker 免费搭建网址导航网站
史上最全的工作流引擎 Activiti 学习教程(值得收藏)
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/05/23
2.5K0
史上最全的工作流引擎 Activiti 学习教程(值得收藏)
[译] 对比 React Hooks 和 Vue Composition API
原文:https://dev.to/voluntadpear/comparing-react-hooks-with-vue-composition-api-4b32
江米小枣
2020/06/15
6.7K0
读猿码系列——2. 搞懂Etcd核心API
小区从管控区调整为防范区了,40多天的封闭后终于可以光明正大地下楼遛狗了!许愿能尽快吃上平价麦当劳,而且每顿都有可口可乐!日拱一卒,让我们开始吧!(长文预警哦)
才浅Coding攻略
2022/12/12
5710
读猿码系列——2. 搞懂Etcd核心API
JAVA相关基础知识复习(超详尽!!值得收藏!!)
JAVA相关基础知识 1、面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。 2.继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)
葆宁
2019/04/18
8470
初识JAVA:JAVA最全基础知识复习(超详尽!!值得收藏!)
1、面向对象的特征 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。 2.继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。 3.封装: 封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。 4. 多态性: 多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
葆宁
2022/05/06
3.5K0
Android面试题含答案「建议收藏」
onCreate() -> onStart() -> onResume() -> onPause() -> onStop() -> onDetroy()
Java架构师必看
2022/04/11
1.5K0
Android面试题含答案「建议收藏」
【建议收藏】历时一年的内网学习笔记合集
自 2020 年 11 月份至 2021 年 10 月份,在这近一年的时间里,笔者更新了自己在学习内网过程中的 30 余篇笔记,并将笔记同步更新到了自己的公众号、博客、CSDN 等平台,特在此整理成合集发布出来。
TeamsSix
2022/09/20
3.3K0
【建议收藏】历时一年的内网学习笔记合集
自然语言处理之词袋模型Bag_of_words
教程地址: https://www.kaggle.com/c/word2vec-nlp-tutorial/overview/part-1-for-beginners-bag-of-words
全栈程序员站长
2022/09/01
2.1K0
自然语言处理之词袋模型Bag_of_words
推荐阅读
相关推荐
一种不错的 BFF Microservice GraphQL/REST API 层的开发方式
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 1. 函数返回channel
    • 1.1 通道做句柄
  • 扇入(fan in): 多个channel 并入一个
    • 阻塞与按序恢复
  • 使用select
    • 扇入(fan In)模式与Select的结合
  • 模式3:通信超时
    • 单次超时
    • 总体超时
    • 使用context.WithTimeout来控制超时退出
    • 周期定时
  • 模式4:自定义退出
    • 安全的退出
  • goroutine的速度
  • Google Search
    • Google Search 1.0
    • Google Search 2.0
    • Google Search 2.1
    • Google Search 3.0
  • callback模式--作为参数
  • 缓存模式-- channel带buffer
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档