go语言goroutines并发模式

面壁偷笑 面壁偷笑     2022-09-17     762

关键词:

并发模式

让我们先来回顾一下boring函数的例子。

func boring(msg string, c chan string) {
   for i := 0; ; i++ {
        c <- fmt.Sprintf("%s %d", msg, i)
        time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
	}
}
           
func main() {	
	c := make(chan string)
	go boring("boring!", c)
 	for i := 0; i < 5; i++ {
    	fmt.Printf("You say: %q
", <-c)
    }
    fmt.Println("You‘re boring; I‘m leaving.")
}

接下来,我会base于上面的这个例子,来介绍各种patterns。

  • 生成器(Generator)

由于go中的channel也是一种变量,所以我们可以通过返回channel的方式来传递结果

func boring(msg string) <-chan string { 
    c := make(chan string)
    go func() { 
        for i := 0; ; i++ {
            c <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
        }
    }()
    return c 
}
func main(){
	c := boring("boring!") 
    for i := 0; i < 5; i++ {
        fmt.Printf("You say: %q
", <-c)
    }
    fmt.Println("You‘re boring; I‘m leaving.")
}

通过这个例子,我们可以很容易想到其他运用返回结果channel的例子,这样做不仅使得程序更加的清晰,而且更加有利于的非阻塞过程的组织,因为我们可以在任何必要的时候通过结果channel读取结果。如此一来,我们可以将boring作为一种服务,就像下面的例子:

func main() {
    joe := boring("Joe")
    ann := boring("Ann")
    for i := 0; i < 5; i++ {
        fmt.Println(<-joe)
        fmt.Println(<-ann)
    }
    fmt.Println("You‘re both boring; I‘m leaving.")
}
  • 多路复合(Multiplexing)

func fanIn(input1, input2 <-chan string) <-chan string {
    c := make(chan string)
    go func() { for { c <- <-input1 } }()
    go func() { for { c <- <-input2 } }()
    return c
}
func main() {
    c := fanIn(boring("Joe"), boring("Ann"))
    for i := 0; i < 10; i++ {
        fmt.Println(<-c)
    }
    fmt.Println("You‘re both boring; I‘m leaving.")
}

我们通过fanIn函数将两个boring函数返回的结果channel给复合到了一个channel中,这样我们可以看到在main函数中通过复合后的channel读出的结果数据将是随机的。下面这张图很形象地的展现了多路复合模式的过程。

技术分享

 

  • 选择(Select)

Go中的select其实和Unix/Linux下的多路复用的select在思想上有异曲同工之妙,我们可以通过Select来做很多很美妙的事情。首先,我们来改写fanin方法,把它改写为使用select的版本:

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
}

这里的select将同时监听多个channel,只要有其中一个channel可以读取数据,那么select就将解除阻塞状态,运行相应case下的代码。如果您写过一些高性能的并发程序,那么您一定早就发现select真乃神器,select不仅可以简化代码清晰逻辑,而且可以减少IO并发开销,大大增大并发吞吐量。

  • 超时(Timeout)

在goroutines中,有时候可能会因为等待某个channel而长期阻塞某个goroutine,所以我们需要为之增加超时的功能。下面例子将使用select实现超时功能。

func main() {
    c := boring("Joe")
    for {
        select {
        case s := <-c:
            fmt.Println(s)
        case <-time.After(1 * time.Second):
            fmt.Println("You‘re too slow.")
            return
        }
    }
}

这里的time是go提供的一个库,After方法将返回一个在相应时间之后可以读取的channel,这样我们使用select就可以很方便得实现超时处理的功能。

  • 退出

那么我们怎么来控制一个goroutine,使它可以结束自己的使命正常结束呢?其实很简单,同样我们使用select来实现这个功能。

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

通过在boring的循环中增加一个select,在main中我们便可以通过向quit 写入数据的方式来控制boring的退出。换句话来讲,其实就是做到了不同goroutines间的一个交流罢了。

  • 菊花链(Daisy-chain)

要说清楚什么是菊花链,让我们先看一幅图

技术分享

我们看图说话,图中的gopher是一个一个channel,这些channel从头到尾连了起来。但我们把一个数据放到channel的头部的时候,通过传递,我们便可以从channel的尾部读出数据。是不是觉得这很像大家小时候玩的传悄悄话的游戏??具体实例如下:

func f(left, right chan int) {
    left <- 1 + <-right
}

func main() {
    const n = 100000
    leftmost := make(chan int)
    right := leftmost
    left := leftmost
    for i := 0; i < n; i++ {
        right = make(chan int)
        go f(left, right)
        left = right
    }
    go func(c chan int) { c <- 1 }(right)
    fmt.Println(<-leftmost)
}

上面代码初始化了100000个channel,并把他们按照顺序连接起来。最后向最右边的channel写入一个数据,从最左边的channel读出来。这种菊花链的模型非常适合作为过滤器filter来使用,通过channel来连接filter会显得十分方便。

 

go语言基础之并发(代码片段)

...和你朋友都在用微信和女朋友聊天)。Go语言的并发通过goroutine实现。goroutine类似于线程,属于用户态的线程,我们可以根据需要创建成千上万个goroutine并发工作。goroutine是由Go语言的运行时(runtime)调度完成,而线程是由操作... 查看详情

19.go语言基础之并发(代码片段)

...windows中360在杀毒,同时你也在写代码)Go语言的并发通过goroutine实现。goroutine类似于线程,属于用户态的线程,我们可以根据需要创建成千上万个goroutine并发工作。goroutine是由Go语言的运行时(runtime)调度完成,而线程是由操作系... 查看详情

09.go语言并发(代码片段)

...runtime),从语言上支持了并发的特性。Go语言的并发通过goroutine特性完成。goroutine类似于线程,但是可以根据需要创建多个goroutine并发工作。goroutine是由Go语言的运行时调度完成,而线程是由操作系统调度完成。Go语言还提供chann... 查看详情

go语言并发(代码片段)

...多个任务并行:同一时刻执行多个任务Go语言的并发通过goroutine实现。goroutine类似于线程,属于用户态的线程,我们可以根据需要创建成千上万个goroutine并发工作。goroutine是由Go语言的运行时调度完成,而线程是由操作系统调度... 查看详情

go语言之并发示例-pool

...的通道实现一个资源池,这个资源池可以管理在任意多个goroutine之间共享的资源,比如网络连接、数据库连接等,我们在数据库操作的时候,比较常见的就是数据连接池,也可以基于我们实现的资源池来实现。可以看出,资源池... 查看详情

《go语言实战》摘录:6.2并发-goroutine

6.2goroutine 查看详情

go语言学习笔记—进阶—并发编程:轻量级线程goroutine——并发与并行

...分布式程序等。go语言的并发是指多线程并发,通过goroutine完成goroutine类似线程,可以根据需要创建多个goroutine并发工作goroutine是由go语言的编译器运行时(runtime)调度完成,而线程是由操作系统调度完成多个goroutine之... 查看详情

go语言基础之并发和网络

1、goroutine在这章中将展示Go使用channel和goroutine开发并行程序的能力。goroutine是Go并发能力的核心要素。但是,goroutine到底是什么?叫做goroutine是因为已有的短语——线程、协程、进程等等——传递了不准确的含义。goroutine有简单... 查看详情

go语言系列之并发编程(代码片段)

...和你朋友都在用微信和女朋友聊天)。Go语言的并发通过goroutine实现。goroutine类似于线程,属于用户态的线程,我们可以根据需要创建成千上万个goroutine并发工作。goroutine是由Go语言的运行时(runtime)调度完成,而线程是由操作... 查看详情

go语言学习笔记—进阶—并发编程:为函数创建goroutine(代码片段)

使用go关键字为一个函数创建一个goroutine。函数与goroutine是一对多的关系,即可以为一个函数创建多个goroutine,一个goroutine必定只对应一个函数。为普通函数创建goroutine格式为一个go函数创建goroutine,写法如下:go... 查看详情

7.1go语言中什么是goroutines(代码片段)

基本概念Goroutine是一个被Go运行时管理的轻量级线程(AgoroutineisalightweightthreadmanagedbytheGoruntime)。为什么没有采用现有的表示并发名词来解释Go语言中的并发呢?因为现有的线程、协程、进程等都无法准确表达Goroutine。Goroutines使用... 查看详情

15.go语言“避坑”与技巧(代码片段)

...生错误的地方及Go语言本身的使用技巧进行总结和归纳。goroutine(Go语言并发)如何使用才更加高效?Go语言原生支持并发是被众人津津乐道的特性。goroutine早期是Inferno操作系统的一个试验性特性,而现在这个特性与操作系统一... 查看详情

go语言并发与通道的运用(代码片段)

在go语言中我们可以使用goroutine开启并发。goroutine是轻量级线程,goroutine的调度是由Golang运行时进行管理的。goroutine语法格式:go函数名(参数列表)实例1:packagemainimport("fmt""time")funcsay(sstring)fori:=0;i< 查看详情

go基础--goroutine和channel

goroutine在go语言中,每一个并发的执行单元叫做一个goroutine这里说到并发,所以先解释一下并发和并行的概念:并发:逻辑上具备同时处理多个任务的能力并行:物理上在同一时刻执行多个并发任务当一个程序启动时,其主函数... 查看详情

go语言goroutine(代码片段)

前言        go语言对于并发编程有原生的支持。        如下代码:创建10个协程并发执行。packagemainimport( "fmt" "time")funcmain() fori:=0;i<10;i++ gofunc(iint) fmt.Printf("hellofromgoroutine 查看详情

go并发编程基础-channel(代码片段)

协程(Goroutine)Go语言中没有线程的概念,只有协程,也称为goroutine。相比线程来说,协程更加轻量,一个程序可以随意启动成千上万个goroutine。goroutine被Goruntime所调度,这一点和线程不一样。也就是说,Go语言的并发是由Go自己... 查看详情

go语言学习笔记—进阶—并发编程:go语言的协程goroutine,与普通程序的协程coroutine

拿Python程序的coroutine与Go程序的goroutine做比较,两者都可以把函数或语句运行在独立的环境,但还是有不同点:goroutine可能并行执行,但coroutine始终顺序执行狭义地说,goroutine可能发生在多线程环境下。goroutine... 查看详情

go14--并发concurrency,goroutine,channel

...的高并发而忍不住跃跃欲试,但其实从源码的解析来看,goroutine只是由官方实现的超级“线程池”而已。不过话说回来,每个实例4-5KB的栈内存占用和由于实现机制而大幅减少的创建和销毁开销,是制造Go号称的高并发的根本原... 查看详情