Go语言基础:并发
2016-09-21 08:52
387 查看
并行与并发
理论式的概念:
并行:多件事在同一时刻发生。并发:多件事在同一时间间隔发生。
5岁小孩都能看懂的解释:
摘自:http://www.cnblogs.com/yangecnu/p/3164167.html 和 Concurrent and Parallel Programming
上文如果用程序员的语言来讲,CPU处理器相当于上图的咖啡机的角色,任务相当于队列中的人。
并发与并行的区别:
一定要仔细阅读此文:http://blog.csdn.net/coolmeme/article/details/9997609 。这篇文章提到了网络服务器并发连接数、吐吞量、宽带的概念,对于初学者应该很受用。Goruntine
goruntine原理
我们知道Go从语言层面就支持了并发,而goruntine是go并发设计的核心。goruntine说到底是协程【Go Web 编程里是线程,也是对的,因为协程类似于用户态线程】。具体原理实现参考:1. 以goroutine为例看协程的相关概念
2. goroutine与调度器
3. 廖雪峰:协程
4. 知乎:协程的好处是什么?
5. 知乎:golang的goroutine是如何实现的?
这些参考文章建议读者好好看看。
了解了协程、goruntine的实现机制,接下来学习如何启动goruntine。
启动goruntine
goroutine 通过关键字 go 就启动了一个 goroutine。go hello(a, b, c)//普通函数前加go
例子:
package main import ( "fmt" "runtime" ) func say(s string) { for i := 0; i < 5; i++ { runtime.Gosched() //表示让cpu将控制权给其他人 fmt.Println(s) } } func main() { runtime.GOMAXPROCS(1) go say("world") say("hello") }
输出:
hello world hello world hello world hello world hello
很简单,在函数前加一个go关键词就启动了goruntine。
Channel
channel是什么
channel是一种通信通道,goruntine之间的数据通信通过channel来实现。goruntine通过channel发送或者接收消息。channel的基本操作语法:
cl := make(chan int) //创建一个无缓冲的int型channel,可以根据需求创建bool、string等类型的channel c1 := make(chan int, 4) //创建有缓冲的int型channel cl <- x //发送x到channel cl x := <- cl //从cl中接收数据,并赋值给x
无缓冲的例子:
package main import ( "fmt" "time" ) func sendChan(cl chan string) { fmt.Println("[send_start]") cl <- "hello world" // 向cl中加数据,如果没有其他goroutine来取走这个数据,那么挂起sendChan, 直到getChan函数把"hello world"这个数据拿走 fmt.Println("[send_end]") } func getChan(cl chan string) { fmt.Println("[get_start]") s := <-cl // 从cl取数据,如果cl中还没放数据,那就挂起getChan线程,直到sendChan函数中放数据为止 fmt.Println("[get_end]" + s) } func main() { cl := make(chan string) go sendChan(cl) go getChan(cl) time.Sleep(time.Second) }
输出:
[send_start] [get_start] [get_end]hello world [send_end]
上面的例子存在3个goruntine,注意main也在一个goruntine中。如果函数main中没有 time.Sleep(time.Second),你会发现什么输出都不会有,为什么呢?是因为另外两个goruntine还没来得及跑,主函数main就已经退出了。
所以需要让main等一下,time.Sleep(time.Second)就是让main停顿一秒再输出。
无缓冲的channel的接收和发送都是阻塞的,也就是说:
数据流入无缓冲信道, 如果没有其他goroutine来拿走这个数据,那么当前线阻塞
从无缓冲信道取数据,必须要有数据流进来才可以,否则当前goroutine阻塞
有缓冲的例子:
package main import ( "fmt" "time" ) func sendChan(cl chan int, len int) { fmt.Println("sendChan_enter") for i := 0; i < len; i++ { fmt.Println("# ", i) cl <- i //cl的存储第4个数据的时候,会阻塞当前goruntine,直到其它goruntine取走一个或多个数据 } fmt.Println("sendChan_end") } func getChan(cl chan int, len int) { fmt.Println("getChan_enter") for i := 0; i < len; i++ { data := <-cl fmt.Println("$ ", data)//当cl的数据为空时,阻塞当前goruntine,直到新的数据写入cl } fmt.Println("getChan_end") } func main() { cl := make(chan int, 3)// 写入3个元素都不会阻塞当前goroutine, 存储个数达到4的时候会阻塞 go sendChan(cl, 10) go getChan(cl, 5) time.Sleep(time.Second) }
输出:
sendChan_enter # 0 # 1 # 2 # 3 getChan_enter $ 0 $ 1 $ 2 $ 3 # 4 # 5 # 6 # 7 # 8 $ 4 getChan_end
为什么sendChan_end没有输出?
getChan取完5个数据后,getChan这个goruntine就会挂起,而sendChan线程因为数据填满,无法将剩余的数据写入chanl而挂起,最后因main所在的goruntine超时1秒结束而结束。故而看不到sendChan_end的输出。
有缓冲的channel是可以无阻塞的写入,当缓冲填满时,再次写入新的数据时,当前goruntine会发生阻塞,直到其它goruntine从channel中取走一些数据:
有缓冲的channel可以无阻塞的获取数据,当数据取空时,再次取新的数据时,当前的goruntine会发生阻塞,直到其它goruntine往channel写入新的数据
close
生产者【发送channel的goruntine】通过关键字 close 函数关闭 channel。关闭 channel 之后就无法再发送任何数据了, 在消费方【接收channel的goruntine】可以通过语法 v, ok := <-ch 测试 channel 是否被关闭。如果 ok 返回 false,那么说明 channel 已经没有任何数据并且已经被关闭。不过一般用得少,网上关于它的描述也不多。
select
语法结构类似于switch。select { case cl<-x: go语句 case <-cl: go语句 default: //可选, go语句 }
每个case只能是channel的获取或者写入,不能是其它语句。
当每个case都无法执行,如果有default,执行default;如果没有default,当前goruntine阻塞。
当多个case都可以执行的时候,随机选出一个执行。
关于select的用法,强烈推荐阅读:【GOLANG】Go语言学习-select用法
相关文章推荐
- Go语言基础学习三-简单的代码分析(并发)
- GO_11:GO语言基础之并发concurrency
- GO语言基础之并发concurrency
- 深入Go语言网络库的基础实现
- Go 语言基础教程:10分钟入门
- Golang 入门系列(三)Go语言基础知识汇总
- 深入Go语言网络库的基础实现
- Go语言基础:method
- go语言 新手学习笔记 go基础教程
- Go语言并发
- Go语言并发与并行学习笔记(二)
- Go语言_并发篇
- Golang语言快速上手到综合实战(Go语言、Beego框架、高并发聊天室、豆瓣电影爬虫) 下载
- go语言中的并发处理
- go语言学习-并发编程
- go语言坑之并发访问map
- Go语言基础:array、slice、make和new操作、map
- Go语言并发与并行学习笔记(一)
- Go语言基础语法
- Go语言基础学习教程