开源库「go home」聚焦Go语言技术栈与面试题,以协助Gopher登上更大的舞台,欢迎go home~

背景介绍

大家都知道进程是操作系统资源分配的基本单位,有独立的内存空间,线程可以共享同一个进程的内存空间,所以线程相对轻量,上下文切换开销也小。虽然线程已经比较轻量了,但还是占近1M的内存,而今天介绍的有“轻量级线程”之称的Goroutine,可以小至几十K甚至几K,切换的开销更小。

除此之外,在传统Socket编程时,需要维护一个线程池来为每个Socket收发包分配线程,而且需要将CPU与线程数建立对应关系,确保每个任务都能被及时分配给CPU,而Go程序可以智能地将goroutine中的任务分配到CPU。

如何使用

我们现在假设一个场景,你是一家公司老总,每天要花两小时处理邮件,六小时开会,那么,程序可以这样编写。

func main() {

	time.Sleep(time.Hour * 2) //处理邮件
time.Sleep(time.Hour * 6) //开会 fmt.Println("工作完成了")
}

运行一下

果然,要8小时才能完成工作,那么怎么简化工作呢?没错,请一个助理小姐姐帮忙,就让她来处理邮件,这样你就只需6小时开会就行了。

开始写代码吧,先来定义一个助理函数。

func assistant() {
time.Sleep(time.Hour * 2)
}

然后在主函数用一条神器的命令go调用它,这样助理的耗时久不再占用你的时间了。

func main() {

	go assistant()

	time.Sleep(time.Hour * 6) //开会

	fmt.Println("工作完成了")
}

运行一下

真的只花了六个小时就完成工作了。各位看官们,看到没,这就是协程,只需要go命令加上函数名,就这么简单。

但是我知道勤奋的你是不会满足于现状的。

匿名函数

另外,既然goroutine支持普通函数,当然也就支持匿名函数。

go func() {
time.Sleep(time.Hour * 2)
}()

协程间如何通讯

虽然我们可以轻松地创建一堆协程,但是不能通信的协程是没有灵魂的。假如助理正在帮你处理邮件时,你突然想请她喝奶茶,那是不是要通知她?

那怎么通知呢?这就引出了大名鼎鼎的channel,汉译“通道”,顾名思义它的作用就是在协程之间建立通道,一端可以将数据源源不断地传送到通道的另一端。

而声明方式也非常简单,只需要make一下。拿下方代码为例,它代表初始化一个通道类型变量,并且通道里只能存放string类型的数据。

ch := make(chan string)

初始化完成后,要想与协程函数建立连接,得先把chan变量传给协程函数。

go assistant(ch)

当然,协程函数要能接收chan才行,我们纵深到函数内部,看看都干了些什么。

func assistant(ch chan string) {

	go func() {
for {
fmt.Println("看了一封邮件")
time.Sleep(time.Second * 1)
}
}() msg := <-ch
if msg == "喝奶茶去呗" {
ch <- "好啊"
}
}

函数内部又起了一个协程专门处理邮件,同时另外一边等待老板通知。细心的你应该看出如何取通道数据了,没错,只需要在通道变量前加上<-符号就可以将值取出,同样的,符号加在后面就是往通道塞数据。

ch <- "pingyeaa"
<- ch

如果通道没有数据,消费端就会一直阻塞,直到有数据为止。当然编译器是很聪明的,在编译的时候如果发现没有地方往通道里塞数据,它就会panic,提示死锁。

fatal error: all goroutines are asleep - deadlock!

继续来看代码,大致意思就是老板如果发“喝奶茶去呗”,就返回“好啊”,因为通道里一开始是没数据的,所以该协程会一直阻塞,直到主函数往通道中写入了消息。

现在来看下主函数的实现逻辑,声明通道和传入通道变量就不再赘述了,我们只需要等待5秒钟之后往通道里写入喝奶茶消息即可。因为刚才assistant协程接收到消息后会往ch写入“好啊”消息,所以主函数在发完请求之后应该再读取从助理那边传递来的消息。

ch := make(chan string)

go assistant(ch)

time.Sleep(time.Second * 5)
ch <- "喝奶茶去呗" resp := <-ch
fmt.Println(resp)

同样,主函数的<-ch也会一直阻塞,直到助理回复消息。另外有两点需要注意,第一,如果main函数赶在goroutine之前执行完毕,那么goroutine也会销毁;第二,main也是goroutine。

最后,关闭通道,其实通道关闭不是必须的,它与文件不同,如果没有goroutine使用到channel,就会自动销毁,而close的作用是用来通知通道的另一端不再发送消息了,另一端可以通过<-ch的第二个参数来获取通道关闭情况。

close(ch)

data, ok := <-ch

通道的多路复用select

刚才的示例中的<-ch只能读取通道的一条消息,如果通道里不止一条消息,该怎么读取呢?

应该很多同学跟我一样想到的是遍历,没错,遍历确实可以拿到通道数据。

for {
fmt.Println(<-ch)
}

也可以这么遍历。

for d := range ch {
fmt.Println(d)
}

但是,如果需要同时接收多个通道数据该怎么办?循环中接收两个通道变量?

for {
data, ok := <-ch1
data, ok := <-ch2
}

这种方式虽然可以取出数据,但是性能较差,官方给我们提供的select关键词就是专门用来解决多通道数据读取问题的,语法与switch非常相似。select会将多个通道传来的数据分发到不同的处理逻辑中。

func main() {

	ch1 := make(chan int)
ch2 := make(chan int) go func() {
for {
select {
case d := <-ch1:
fmt.Println("ch1", d)
case d := <-ch2:
fmt.Println("ch2", d)
}
}
}() ch1 <- 1
ch1 <- 2
ch2 <- 2
ch1 <- 3
}

模拟超时

除此之外,有些情况下我们不希望通道阻塞太久,假设5秒钟还取不出通道的数据,就超时退出,那我们可以使用time.After方法来实现。time.After会返回一个通道类型,它的作用是传入一个目标时间(比如5s),我们在5秒后就可以通过通道获取预设置的超时通知,这样就达到了定时器的目的。

func main() {

	ch1 := make(chan int)
ch2 := make(chan int) go func() {
for {
select {
case d := <-ch1:
fmt.Println("ch1", d)
case d := <-ch2:
fmt.Println("ch2", d)
case <-time.After(time.Second * 5):
fmt.Println("接收超时")
}
}
}() time.Sleep(time.Second * 6)
}

通道关闭延伸阅读

已关闭的通道再发送数据会触发panic

ch := make(chan int)
close(ch)
ch <- 1
panic: send on closed channel

通道设置长度

可以通过make方法设置通道长度,作为缓冲区,通道满时生产者端会阻塞,通道取空后消费端会阻塞。

ch := make(chan int, 3)

ch <- 1
ch <- 2
ch <- 2
ch <- 2 fmt.Println(len(ch))

已关闭的通道依然可以读取数据

ch := make(chan int, 3)

ch <- 1
ch <- 2
ch <- 2 close(ch) for d := range ch {
fmt.Println(d)
}

感谢大家的观看,如果觉得文章对你有所帮助,欢迎关注公众号「平也」,聚焦Go语言与技术原理。

「一闻秒懂」你了解goroutine和channel吗?的更多相关文章

  1. 对于前端,「微信小程序」其实不美好

    微信小程序开放公测了,9月底我曾经写过一篇 「微信小程序」来了,其中最后一句:"谢天谢地,我居然还是个前端". 这种火爆的新事物总是令人激动,感谢这个时代. 但是,当我真作为开发者 ...

  2. macOS安装「oh my zsh」

    目前常用的 Linux 系统和 OS X 系统的默认 Shell 都是 bash,但是真正强大的 Shell 是深藏不露的 zsh, 这货绝对是马车中的跑车,跑车中的飞行车,史称『终极 Shell』, ...

  3. 报名|「OneAPM x DaoCloud」技术公开课:Docker性能监控!

    如今,越来越多的公司开始 Docker 了,「三分之二的公司在尝试了 Docker 后最终使用了它」,也就是说 Docker 的转化率达到了 67%,同时转化时长也控制在 60 天内. 既然 Dock ...

  4. 企业运营对 DevOps 的「傲慢与偏见」

    摘要:出于各种原因,并非所有人都信任 DevOps .有些人觉得 DevOps 只不过给开发者改善产品提供了一个途径而已,还有的人觉得 DevOps 是一堆悦耳的空头支票,甚至有人认为 DevOps ...

  5. 「前端开发者」如何把握住「微信小程序」这波红利?

    由于前两周一直在老家处理重要事情,虽然朋友圈被「微信小程序」刷爆了,但并没有时间深入了解. 昨天回广州之后,第一件事情就是把「微信小程序」相关的文章.开发文档.设计规范全部看了一遍,基本上明白了「微信 ...

  6. 「花田对」CSDN程序员专场——谁来拯救技术宅!_豆瓣

    「花田对」CSDN程序员专场--谁来拯救技术宅!_豆瓣 「花田对」CSDN程序员专场--谁来拯救技术宅!

  7. Objective-C 实用关键字详解1「面试、工作」看我就 🐒 了 ^_^.

    在写项目 或 阅读别人的代码(一些优秀的源码)中,总能发现一些常见的关键字,随着编程经验的积累大部分还是知道是什么意思 的. 相信很多开发者跟我当初一样,只是基本的常用关键字定义属性会使用,但在关键字 ...

  8. LOJ6003 - 「网络流 24 题」魔术球

    原题链接 Description 假设有根柱子,现要按下述规则在这根柱子中依次放入编号为的球. 每次只能在某根柱子的最上面放球. 在同一根柱子中,任何2个相邻球的编号之和为完全平方数. 试设计一个算法 ...

  9. LOJ6002 - 「网络流 24 题」最小路径覆盖

    原题链接 Description 求一个DAG的最小路径覆盖,并输出一种方案. Solution 模板题啦~ Code //「网络流 24 题」最小路径覆盖 #include <cstdio&g ...

随机推荐

  1. Pending 打断点

    pending 英['pendɪŋ],美['pɛndɪŋ] a. 未决定的, 待决的, 行将发生的, 向外伸出的prep. 在等待...之际, 直到...时为止, 在...期间, 在...过程中 pe ...

  2. 选择tomcat时候提示Project facet Java version 1.8 is not supported.解决办法

    是因为jdk版本不一致导致的,如何解决? 方法一: 选中项目Properties,选择Project Facets,右击选择Java,Change Version 方法二: 在项目的目录下有一个.se ...

  3. 一个简单的方法去掉angular application中URLs的hashtag

    本文转载自:Pretty URLs in AngularJS: Removing the # By default, AngularJS will route URLs with a hashtag. ...

  4. MySQL基础篇(07):用户和权限管理,日志体系简介

    本文源码:GitHub·点这里 || GitEE·点这里 一.MySQL用户 1.基础描述 在数据库的使用过程中,用户作为访问数据库的鉴权因素,起到非常重要的作用,安装MySQL时会自动生成一个roo ...

  5. hdu1856 并查集

    题目链接:http://icpc.njust.edu.cn/Problem/Hdu/1856/ 题目就是要求并查集中各树的大小的最大值,我们只要在根节点处存树的大小就可以,合并也是合并根节点的数,最后 ...

  6. VMware安装Ubutun之SSH为何安装不上之谜

    一把心酸泪,鼻涕泪两行.谁人解我苦中苦,原是SSH安装不上去. 多方找寻,想要寻求解答. 首先我想到一个办法,找到进程,并且狠狠的用RM把它移除掉. NO,这个方法最后竟然不行,文件删了,锁还是获取不 ...

  7. Servlet(四)----HTTP、Response、servletContent

    ##  HTTP协议: 1.请求消息:客户端发送给服务器端的数据 *  数据格式: 1.请求行 2.请求头     3.请求空行 4.请求体 2.响应消息:服务器端发送给客户端的数据 *  数据格式: ...

  8. openfire广播broadcast插件怎么发送消息给所有用户(包括在线和离线)

    openfire广播broadcast插件怎么发送消息给所有用户(包括在线和离线): 打开openfire管理界面,找到服务器系统属性,添加一个属性(属性名:plugin.broadcast.all2 ...

  9. Python第五章-内置数据结构05-集合

    Python内置数据结构 五.集合(set) python 还提供了另外一种数据类型:set. set用于包含一组无序的不重复对象.所以set中的元素有点像dict的key.这是set与 list的最 ...

  10. effective-java学习笔记---使用枚举类型替代整型常量34

    1.要将数据与枚举常量相关联,请声明实例属性并编写一个构造方法,构造方法带有数据并将数据保存在属性中. // Enum type with data and behavior public enum ...