Go语言并发与并行学习笔记(三)
转:http://blog.csdn.net/kjfcpua/article/details/18265475
Go语言并发的设计模式和应用场景
以下设计模式和应用场景来自Google IO上的关于Goroutine的PPT:https://talks.golang.org/2012/concurrency.slide
本文的示例代码在: https://github.com/hit9/Go-patterns-with-channel
生成器
在Python中我们可以使用yield
关键字来让一个函数成为生成器,在Go中我们可以使用信道来制造生成器(一种lazy load类似的东西)。
当然我们的信道并不是简单的做阻塞主线的功能来使用的哦。
下面是一个制作自增整数生成器的例子,直到主线向信道索要数据,我们才添加数据到信道
func xrange() chan int{ // xrange用来生成自增的整数
var ch chan int = make(chan int) go func() { // 开出一个goroutine
for i := 0; ; i++ {
ch <- i // 直到信道索要数据,才把i添加进信道
}
}() return ch
} func main() { generator := xrange() for i:=0; i < 1000; i++ { // 我们生成1000个自增的整数!
fmt.Println(<-generator)
}
}
这不禁叫我想起了Python中可爱的xrange
, 所以给了生成器这个名字!
服务化
比如我们加载一个网站的时候,例如我们登入新浪微博,我们的消息数据应该来自一个独立的服务,这个服务只负责 返回某个用户的新的消息提醒。
如下是一个使用示例:
func get_notification(user string) chan string{
/*
* 此处可以查询数据库获取新消息等等..
*/
notifications := make(chan string) go func() { // 悬挂一个信道出去
notifications <- fmt.Sprintf("Hi %s, welcome to weibo.com!", user)
}() return notifications
} func main() {
jack := get_notification("jack") // 获取jack的消息
joe := get_notification("joe") // 获取joe的消息 // 获取消息的返回
fmt.Println(<-jack)
fmt.Println(<-joe)
}
多路复合
上面的例子都使用一个信道作为返回值,可以把信道的数据合并到一个信道的。 不过这样的话,我们需要按顺序输出我们的返回值(先进先出)。
如下,我们假设要计算很复杂的一个运算 100-x
, 分为三路计算, 最后统一在一个信道中取出结果:
func do_stuff(x int) int { // 一个比较耗时的事情,比如计算
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) //模拟计算
return 100 - x // 假如100-x是一个很费时的计算
} func branch(x int) chan int{ // 每个分支开出一个goroutine做计算并把计算结果流入各自信道
ch := make(chan int)
go func() {
ch <- do_stuff(x)
}()
return ch
} func fanIn(chs... chan int) chan int {
ch := make(chan int) for _, c := range chs {
// 注意此处明确传值
go func(c chan int) {ch <- <- c}(c) // 复合
} return ch
} func main() {
result := fanIn(branch(1), branch(2), branch(3)) for i := 0; i < 3; i++ {
fmt.Println(<-result)
}
}
select监听信道
Go有一个语句叫做select
,用于监测各个信道的数据流动。
如下的程序是select的一个使用例子,我们监视三个信道的数据流出并收集数据到一个信道中。
func foo(i int) chan int {
c := make(chan int)
go func () { c <- i }()
return c
} func main() {
c1, c2, c3 := foo(1), foo(2), foo(3) c := make(chan int) go func() { // 开一个goroutine监视各个信道数据输出并收集数据到信道c
for {
select { // 监视c1, c2, c3的流出,并全部流入信道c
case v1 := <- c1: c <- v1
case v2 := <- c2: c <- v2
case v3 := <- c3: c <- v3
}
}
}() // 阻塞主线,取出信道c的数据
for i := 0; i < 3; i++ {
fmt.Println(<-c) // 从打印来看我们的数据输出并不是严格的1,2,3顺序
}
}
有了select, 我们在 多路复合中的示例代码中的函数fanIn
还可以这么来写(这样就不用开好几个goroutine来取数据了):
func fanIn(branches ... chan int) chan int {
c := make(chan int) go func() {
for i := 0 ; i < len(branches); i++ { //select会尝试着依次取出各个信道的数据
select {
case v1 := <- branches[i]: c <- v1
}
}
}() return c
}
使用select的时候,有时需要超时处理, 其中的timeout信道相当有趣:
timeout := time.After(1 * time.Second) // timeout 是一个计时信道, 如果达到时间了,就会发一个信号出来 for is_timeout := false; !is_timeout; {
select { // 监视信道c1, c2, c3, timeout信道的数据流出
case v1 := <- c1: fmt.Printf("received %d from c1", v1)
case v2 := <- c2: fmt.Printf("received %d from c2", v2)
case v3 := <- c3: fmt.Printf("received %d from c3", v3)
case <- timeout: is_timeout = true // 超时
}
}
结束标志
在Go并发与并行笔记一我们已经讲过信道的一个很重要也很平常的应用,就是使用无缓冲信道来阻塞主线,等待goroutine结束。
这样我们不必再使用timeout。
那么对上面的timeout来结束主线的方案作个更新:
func main() { c, quit := make(chan int), make(chan int) go func() {
c <- 2 // 添加数据
quit <- 1 // 发送完成信号
} () for is_quit := false; !is_quit; {
select { // 监视信道c的数据流出
case v := <-c: fmt.Printf("received %d from c", v)
case <-quit: is_quit = true // quit信道有输出,关闭for循环
}
}
}
菊花链
简单地来说,数据从一端流入,从另一端流出,看上去好像一个链表,不知道为什么要取这么个尴尬的名字。。
菊花链的英文名字叫做: Daisy-chain, 它的一个应用就是做过滤器,比如我们来筛下100以内的素数(你需要先知道什么是筛法)
程序有详细的注释,不再说明了。
/*
* 利用信道菊花链筛法求某一个整数范围的素数
* 筛法求素数的基本思想是:把从1开始的、某一范围内的正整数从小到大顺序排列,
* 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。
* 依次类推,直到筛子为空时结束
*/
package main import "fmt" func xrange() chan int{ // 从2开始自增的整数生成器
var ch chan int = make(chan int) go func() { // 开出一个goroutine
for i := 2; ; i++ {
ch <- i // 直到信道索要数据,才把i添加进信道
}
}() return ch
} func filter(in chan int, number int) chan int {
// 输入一个整数队列,筛出是number倍数的, 不是number的倍数的放入输出队列
// in: 输入队列
out := make(chan int) go func() {
for {
i := <- in // 从输入中取一个 if i % number != 0 {
out <- i // 放入输出信道
}
}
}() return out
} func main() {
const max = 100 // 找出100以内的所有素数
nums := xrange() // 初始化一个整数生成器
number := <-nums // 从生成器中抓一个整数(2), 作为初始化整数 for number <= max { // number作为筛子,当筛子超过max的时候结束筛选
fmt.Println(number) // 打印素数, 筛子即一个素数
nums = filter(nums, number) //筛掉number的倍数
number = <- nums // 更新筛子
}
}
随机数生成器
信道可以做生成器使用,作为一个特殊的例子,它还可以用作随机数生成器。如下是一个随机01生成器:
func rand01() chan int {
ch := make(chan int) go func () {
for {
select { //select会尝试执行各个case, 如果都可以执行,那么随机选一个执行
case ch <- 0:
case ch <- 1:
}
}
}() return ch
} func main() {
generator := rand01() //初始化一个01随机生成器 //测试,打印10个随机01
for i := 0; i < 10; i++ {
fmt.Println(<-generator)
}
}
定时器
我们刚才其实已经接触了信道作为定时器, time包里的After
会制作一个定时器。
看看我们的定时器吧!
/*
* 利用信道做定时器
*/ package main import (
"fmt"
"time"
) func timer(duration time.Duration) chan bool {
ch := make(chan bool) go func() {
time.Sleep(duration)
ch <- true // 到时间啦!
}() return ch
} func main() {
timeout := timer(time.Second) // 定时1s for {
select {
case <- timeout:
fmt.Println("already 1s!") // 到时间
return //结束程序
}
}
}
TODO
Google的应用场景例子。
本篇主要总结了使用信道, goroutine的一些设计模式。
Go语言并发与并行学习笔记(三)的更多相关文章
- Go语言并发与并行学习笔记(一)
转:http://blog.csdn.net/kjfcpua/article/details/18265441 如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就 ...
- Go语言并发与并行学习笔记(二)
转:http://blog.csdn.net/kjfcpua/article/details/18265461 Go语言的并发和并行 不知道你有没有注意到一个现象,还是这段代码,如果我跑在两个goro ...
- Go语言学习笔记三: 常量
Go语言学习笔记三: 常量 定义常量 常量就是在声明后不能再修改的量. const x int = 100 const y string = "abc" const z = &qu ...
- 学习笔记(三)--->《Java 8编程官方参考教程(第9版).pdf》:第十章到十二章学习笔记
回到顶部 注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.若有违本之,则本人不负法律责任.违法 ...
- Oracle学习笔记三 SQL命令
SQL简介 SQL 支持下列类别的命令: 1.数据定义语言(DDL) 2.数据操纵语言(DML) 3.事务控制语言(TCL) 4.数据控制语言(DCL)
- VSTO学习笔记(三) 开发Office 2010 64位COM加载项
原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...
- ES6学习笔记<三> 生成器函数与yield
为什么要把这个内容拿出来单独做一篇学习笔记? 生成器函数比较重要,相对不是很容易理解,单独做一篇笔记详细聊一聊生成器函数. 标题为什么是生成器函数与yield? 生成器函数类似其他服务器端语音中的接口 ...
- MySql学习笔记三
MySql学习笔记三 4.DML(数据操作语言) 插入:insert 修改:update 删除:delete 4.1.插入语句 语法: insert into 表名 (列名1,列名2,...) val ...
- openresty 学习笔记三:连接redis和进行相关操作
openresty 学习笔记三:连接redis和进行相关操作 openresty 因其非阻塞的调用,令服务器拥有高性能高并发,当涉及到数据库操作时,更应该选择有高速读写速度的redis进行数据处理.避 ...
随机推荐
- FJNU 1156 Fat Brother’s Gorehowl(胖哥的血吼)
FJNU 1156 Fat Brother’s Gorehowl(胖哥的血吼) Time Limit: 1000MS Memory Limit: 257792K [Description] [题目 ...
- JZs3c2440裸板程序GPIO操作总结
分别用汇编,汇编+C两种方式 ***************************************汇编编程led_on.s********************************** ...
- js 立即执行函数,() .则前面的function 是表达式,不能是函数申明
fnName(); function fnName(){ ... }//正常,因为‘提升’了函数声明,函数调用可在函数声明之前 fnName(); var fnName=function(){ ...
- (转)http接口测试——Jmeter接口测试实例讲解
http://my.oschina.net/hellotest/blog/512482
- Round robin
http://www.computerhope.com/jargon/r/rounrobi.htm Round robin Round robin is a method of distributin ...
- golang 定时器
上网查了下相关资料,基本上都介绍的是github.com\robfig\cron这个包来执行定时任务,试了下确实可以执行.但是此包下没有删 除任务的方法,只有暂停的方法(Stop),若要停止之前的任务 ...
- bzoj2154(莫比乌斯反演)
又是一道经典题. 1.学习了下O(n) 的做法. // // main.cpp // bzoj2154 // // Created by New_Life on 16/7/7. // Copyrigh ...
- OpenGL的glViewPort窗口设置函数实现分屏
之前实现过全景图片查看(OpenGL的几何变换3之内观察全景图),那么我们需要进行分屏该如何实现呢?如下图: 没错就是以前提过的glViewPort函数,废话不多说了,我直接上代码: //从这里开始进 ...
- 转!!Java 基础面试题的剖析: short s1=1;s1 = s1 +1 报错? s1+=1 呢
short s1=1;s1 = s1 +1会报错吗? package common; public class ShortTypeTest { /* * @param args */ publi ...
- iOS开发 差间距滚动
CGFloat fView_Height(UIView *aView) { return aView.frame.size.height; } CGFloat fView_Width(UIView * ...