Go语言并发与并行学习笔记(三)

目录(?)
[-]

  1. Go语言并发的设计模式和应用场景
    1. 生成器
    2. 服务化
    3. 多路复合
    4. select监听信道
    5. 结束标志
    6. 菊花链
    7. 随机数生成器
    8. 定时器
    9. TODO

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的应用场景例子。

时间: 2024-10-09 06:05:36

Go语言并发与并行学习笔记(三)的相关文章

Go语言并发与并行学习笔记(一)

转:http://blog.csdn.net/kjfcpua/article/details/18265441 如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据. 以下是我入门的学习笔记. 首先,并行!=并发, 两者是不同的,可以参考:http://concur.rspace.googlecode.com/hg/talk/concur.html G

JavaScript--基于对象的脚本语言学习笔记(三)

事件处理器 1.一个数据校验表单的例程 <html> <head> <title>js练习</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script type="text/javascript"> String.prototype.trim=function(){ r

Go语言学习笔记(三) [控制结构、内建函数]

日期:2014年7月21日 一.控制结构 1.Go中,只有几个控制结构,它没有do或者while循环,有for,灵活的switch语句和if,在switch中可以接受像for那样可选的初始化语句,另外Go中还提供了类型选择和多路通信转接器的select.Go的控制结构的语法和C相比有所不同,它不需要圆括号,但语句体必须总是包含在大括号内. 2.控制结构语法 1)if-else (1)if后紧跟单个条件 例如:if x > 0 {   //{必须和if在同一行,这是Go语法规定的,如果换行写,编译

Caliburn.Micro学习笔记(三)----事件聚合IEventAggregator和 Ihandle&lt;T&gt;

Caliburn.Micro学习笔记(三)----事件聚合IEventAggregator和 Ihandle<T> 今天 说一下Caliburn.Micro的IEventAggregator和IHandle<T>分成两篇去讲这一篇写一个简单的例子 看一它的的实现和源码 下一篇用它们做一个多语言的demo 这两个是事件的订阅和广播,很强大,但用的时候要小心发生不必要的冲突. 先看一下它的实现思想 在Caliburn.Micro里EventAggregator要以单例的形式出现这样可以

swift学习笔记(三)关于拷贝和引用

在swift提供的基本数据类型中,包括Int ,Float,Double,String,Enumeration,Structure,Dictionary都属于值拷贝类型. 闭包和函数同属引用类型 捕获则为拷贝.捕获即定义这些常量和变量的原作用域已不存在,闭包仍然可以在闭包函数体内引用和修改这些值 class属于引用类型. Array的情况稍微复杂一些,下面主要对集合类型进行分析: 一.关于Dictionary:无论何时将一个字典实例赋给一个常量,或者传递给一个函数方法时,在赋值或调用发生时,都会

【Unity 3D】学习笔记三十四:游戏元素——常用编辑器组件

常用编辑器组件 unity的特色之一就是编辑器可视化,很多常用的功能都可以在编辑器中完成.常用的编辑器可分为两种:原有组件和拓展组件.原有组件是编辑器原生的一些功能,拓展组件是编辑器智商通过脚本拓展的新功能. 摄像机 摄像机是unity最为核心组件之一,游戏界面中显示的一切内容都得需要摄像机来照射才能显示.摄像机组件的参数如下: clear flags:背景显示内容,默认的是skybox.前提是必须在render settings 中设置天空盒子材质. background:背景显示颜色,如果没

Oracle学习笔记三 SQL命令

SQL简介 SQL 支持下列类别的命令: 1.数据定义语言(DDL) 2.数据操纵语言(DML) 3.事务控制语言(TCL) 4.数据控制语言(DCL)               下面是这四种SQL语言的详细笔记: Oracle学习笔记三 SQL命令(二):SQL操作语言类别 Oracle数据类型 创建表时,必须为各个列指定数据类型 以下是 Oracle 数据类型的类别: 字符数据类型 CHAR类型 当需要固定长度的字符串时,使用 CHAR 数据类型. CHAR 数据类型存储字母数字值. CH

VSTO学习笔记(三) 开发Office 2010 64位COM加载项

原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(Automation Executables) 2.Office加载项(COM or Excel Add-In) 3.Office文档代码或模板(Code Behind an Office Document or Template) 4.Office 智能标签(Smart Tags) 本次我们将学习使

马哥学习笔记三十——tomcat

Java体系结构包含四个独立却又彼此相关的技术: Java程序设计语言 Java API Java Class文件格式 JVM: Java Virtual Machine JVM的实现方式: 1.一次性解释器,解释字节码并执行: 2.即时编译器(just-in-time complier) 依赖于更多内存缓存解释后的结果 3.自适应编译器 缓存20%左右代码,提高80%左右的速度: 运行时数据区: 线程私有内存区: 程序计数器 java虚拟机栈 线程共享内存区: 方法区 堆:java自动内存回收