Go的并发之goroutine和channel篇

Go的并发之goroutine和channel篇

进程和线程相信大家已经很熟悉了,为了压榨多核处理器的性能,所有语言都大显神通,Go也不例外,但Go和其他语言又有些不同,需要用户自己控制并分配资源。

goroutine前言

这种机制在Go中成为goroutine,其实一直使用的main()函数就在使用它,在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。

尽管使不使用goroutine是我们控制的,但其本身运行时(runtime)的调度和管理还是由Go来控制的,Go程序会智能地将goroutine中的任务合理分配给每个CPU,我们也不需要操心进程调度和上下文切换的机制

goroutine用法

使用很简单,只需要使用关键字go来定义即可启动一个goroutine,我们创建一个例子试试

demo1.go(不使用goroutine)
package main

import (
	"fmt"
	"time"
)

func goroutineEmp(i int) {
	fmt.Println("Hello, i m ", i)
	time.Sleep(time.Second)
}

func main(){
    startTime := time.Now()
	for i := 0; i < 10; i++ {
		goroutineEmp(i)
	}
	fmt.Println("============")
	spendTime := time.Since(startTime)
	fmt.Println("Spend Time:", spendTime)
}

输出结果

image-20211230101003118

demo1.go(使用goroutine)
package main

import (
	"fmt"
	"time"
)

func goroutineEmp(i int) {
	fmt.Println("Hello, i m ", i)
	time.Sleep(time.Second)
    fmt.Println("wake up!")
}

func main(){
    startTime := time.Now()
	for i := 0; i < 10; i++ {
		go goroutineEmp(i)
	}
	fmt.Println("============")
	spendTime := time.Since(startTime)
	fmt.Println("Spend Time:", spendTime)
}

运行

image-20211230101835062

可以发现goroutineEmp中wakeup还没启动,十个hello部分都完事儿了,是因为同时调用十个goroutine进程在一秒睡醒之前已经运行完事退出了,你多运行几次可能还会发现有的还没来得及打印hello就完事了,那么如何保证运行顺序,又用上让人又爱又恨的并发呢?下面我们引入进程管道的概念

Channel

关于channel有叫管道的,有叫信道的,这都无所谓,英文别错了就行,在并发执行函数中如果不能交换数据好像差点意思,而且这里交换数据可以解决我们的序列问题。你可能会问,不是可以用共享内存来进行数据交换吗,但那个在不同的goroutine中显然会导致数据不一致的问题,为了解决新的问题又要加锁,那还并发啥?

Go给出的方案是用通信的方法代替共享内存,也就是使用管道(Channel)

管道的数据结构是一个先进先出的队列,保证数据收发的顺序

创建方法
channel实例 := make(chan 数据类型)
chan1 := make(chan int)	//创建一个整形的管道
发送&接收
ch := make(chan int)	//声明一个int型的chan
ch <- 0		//管道发送消息(这里就是把0塞进去)
data := <-ch	//<-ch取消息操作,将取出的消息赋给data

注意这里不能直接运行,只是举例,管道信息的收发需要在两个不同的goroutine中进行

我们来改改上面那个demo,引入管道的概念,来保证数据有序

demo2.go
package main

import (
	"fmt"
	"time"
)

func goroutineEmpChan(chan1 chan int) {
	fmt.Println("Hello, i m from chan ", <-chan1)
	time.Sleep(time.Second)
}

func main(){
    startTime := time.Now()
	chan1 := make(chan int)
	for i := 0; i < 10; i++ {
		go goroutineEmpChan(chan1)
		chan1 <- i
	}
	fmt.Println("============")
	spendTime := time.Since(startTime)
	fmt.Println("Spend Time:", spendTime)
}

运行

image-20211230103918229

发现符合预期,但管道(channel)还是有很多特性是不得不说的

我们发现上面的管道相当于拿到数据就丢出去了,并不做存储,如果一次来好几个数据没来得及被人取走怎么办呢?

无缓冲管道

demo3.go
func main(){
    chan2 := make(chan int)

	go func() {
		for i := 0; i < 3; i++ {
			chan2 <- i
			fmt.Println("发送 ", i, " 给管道啦")
			time.Sleep(time.Second)
		}
	}()
	fmt.Println("收到的第一个数据是", <-chan2)
}

为了简单演示,我们只放一个匿名函数来测试,发现第二个数据已经发不出去了,这就好比生产者和消费者之间的关系,你生产了一个资源发给管道,这个管道一次只能收1个,而且如果这个不被消费掉,就不会收第二个,因为他不能存东西,没人消费他就不进货,离谱吧?这个时候我们就引入了有缓冲的管道

有缓冲管道

demo4.go
func main(){
    chan3 := make(chan int, 1)	//这里第二个参数就是channel的长度
	go func() {
		for i := 0; i < 3; i++ {
			chan3 <- i
			fmt.Println("发送 ", i, " 给管道啦")
		}
	}()
	fmt.Println("收到的第一个数据是", <-chan3)
	//fmt.Println("那有没有第二个数据呢?", <-chan3)
}

运行

image-20211230105322395

可以发现我们取一次,也发了两条消息进去,是因为管道长度为1,可以额外装一个消息供消费,如果长度改成2呢?

image-20211230105434554

确实 没毛病,如果你不相信呢,还可以调用len(channel)来查询管道的长度哦

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

上上签i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值