[Go] 通过 17 个简短代码片段,切底弄懂 channel 基础

关于管道 Channel

  1. Channel 用来同步并发执行的函数并提供它们某种传值交流的机制。
  2. Channel 的一些特性:通过 channel 传递的元素类型、容器(或缓冲区)和 传递的方向由“<-”操作符指定。
  3. c <- 123,把值 123 输入到管道 c,<-c,把管道 c 的值读取到左边,value := <-c,这样就是读到 value 变量里面。

管道分类

无缓冲的 与 有缓冲 channel 有着重大差别,那就是一个是同步的(阻塞的) 一个是非同步的(非阻塞的)。

比如:

c1 := make(chan int)       // 无缓冲
c2 := make(chan int,1)     // 有缓冲

例如:c1 <- 1

  • 无缓冲: 不仅仅是向 c1 通道放 1,而是一直要等有别的协程 <-c1 接手了这个参数,那么 c1 <- 1 才会继续下去,要不然就一直阻塞着。
  • 有缓冲: c2 <- 1 则不会阻塞,因为缓冲大小是1(其实是缓冲大小为 0),只有当放第二个值,且第一个还没被人拿走的时候,才会阻塞。

例子 - 无缓冲

演示 无缓冲 和 有缓冲 的 channel 的样子

func test0() {
	/** 演示 无缓存 和 有缓冲 的 channel 的样子 */
	done := make(chan bool)       /** 无缓冲 */
	done1 := make(chan bool, 1)   /** 有缓冲 */
	println(done, done1)
}

演示 无缓冲在同一个 main 里面的 死锁例子

func test1() {
	/** 编译错误 deadlock,阻死 main 进程 */
	/** 演示 无缓冲在同一个 main 里面的 死锁例子 */
	done := make(chan bool)
	done <- true /** 这句是输入值,它会一直阻塞,等待读取 */
	<-done       /** 这句是读取,但是在上面已经阻死了,永远走不到这里 */
	println("完成")
}

演示仅有 输入 语句,但没 读取语句 的死锁例子

func test2() {
	/** 编译错误 deadlock,阻死 main 进程 */
	/** 演示仅有 输入 语句,但没 读取语句 的死锁例子 */
	done := make(chan bool)
	done <- true /** 输入,一直等待读取,哪怕没读取语句 */
	println("完成")
}

演示仅有 读取 语句,但没 输入语句 的死锁例子

func test3() {
	/** 编译错误 deadlock,阻死 main 进程 */
	/** 演示仅有 读取 语句,但没 输入语句 的死锁例子 */
	done := make(chan bool)
	<-done /** 读取输出,前面没有输入语句,done 是 empty 的,所以一直等待输入 */

	println("完成")
}

演示,协程的阻死,不会影响 main

func test4() {
	/** 编译通过 */
	/** 演示,协程的阻死,不会影响 main */
	done := make(chan bool)
	go func() {
		<-done /** 一直等待 */
	}()
	println("完成")

	/**
	 * 控制台输出:
	 *       完成
	 */
}

在 test4 的基础上,无缓冲 channel 在协程 go routine 里面阻塞死

func test5() {
	/** 编译通过 */
	/** 在 test4 的基础上,无缓冲 channel 在协程 go routine 里面阻塞死 */
	done := make(chan bool)
	go func() {
		println("我可能会输出哦") /** 阻塞前的语句 */
		done <- true       /** 这里阻塞死,但是上面那句有可能输出,见 test3 的结论 */
		println("我永远不会输出")
		<-done /** 这句也不会走到,除非在别的协程里面读取,或者在 main */
	}()
	println("完成")
}

编译通过,在 test5 的基础上演示,延时 main 的跑完

func test6() {
	/** 编译通过,在 test5 的基础上演示,延时 main 的跑完 */
	done := make(chan bool)

	go func() {
		println("我可能会输出哦")
		done <- true /** 这里阻塞死 */
		println("我永远不会输出")
		<-done /** 这句也不会走到 */
	}()

	time.Sleep(time.Second * 1) /** 加入延时 1 秒 */
	println("完成")

	/**
	 * 控制台输出:
	 *       我可能会输出哦
	 *       完成
	 */

	/**
	 * 结论:
	 *    如果在 go routine 中阻塞死,也可能不会把阻塞语句前的内容输出,
	 *    因为main已经跑完了,所以延时一会,等待 go routine
	 */
}

演示无缓冲 channel 在 不同的位置里面接收填充和接收

func test7() {
	/** 编译通过,演示无缓冲channel 在 不同的位置里面 接收填充 和 接收*/
	done := make(chan bool)

	go func() {
		done <- true /** 直到,<-done 执行,否则这里阻塞死 */
		println("我永远不会输出,除非 <-done 执行")
	}()

	<-done /** 这里接收,在输出完成之前,那么上面的语句将会走通 */
	println("完成")

	/**
	 * 控制台输出:
	 *       我永远不会输出,除非 <-done 执行
	 *       完成
	 */
}

演示无缓冲 channel 在不同地方接收的影响

func test8() {
	/** 编译通过,演示无缓冲 channel 在不同地方接收的影响 */
	done := make(chan bool)

	go func() {
		done <- true /** 直到,<-done 执行,否则这里阻塞死 */
		println("我永远不会输出,除非 <-done 执行")
	}()

	println("完成")
	<-done /** 这里接收,在输出完成之后 */

	/**
	 * 控制台输出:
	 *       完成
	 *       我永远不会输出,除非 <-done 执行
	 */
}

无缓存的 channel 使用 close 后,不会阻塞

func test9() {
	/** 编译通过 */
	/** 演示,没缓存的 channel 使用 close 后,不会阻塞 */
	done := make(chan bool)
	close(done)
	// done<-true  /** 关闭了的,不能再往里面输入值,否则会 panic */
	<-done /** 这句是读取,但是在上面已经关闭 channel 了,不会阻死 */
	println("完成")
}

无缓存的 channel,在 go routine 里面使用 close 后,不会阻塞

func test10() {
	/** 编译通过 */
	/** 演示,没缓存的 channel,在 go routine 里面使用 close 后,不会阻塞 */
	done := make(chan bool)

	go func() {
		close(done)
	}()

	//done<-true  /** 关闭了的,不能再往里面输入值 */
	<-done /** 这句是读取,但是在上面已经关闭 channel 了,不会阻死 */
	println("完成")
}

例子 - 有缓冲

有缓冲的 channel 不会阻塞的例子

func test11() {
	/** 编译通过 */
	/** 有缓冲的 channel 不会阻塞的例子 */
	done := make(chan bool, 1)
	done <- true
	<-done
	println("完成")
}

有缓冲的 channel 会阻塞的例子

func test12() {
	/** 编译通过 */
	/** 有缓冲的 channel 会阻塞的例子 */
	done := make(chan bool, 1)
	// done<-true /** 注释这句 */
	<-done /** 虽然是有缓冲的,但是在没输入的情况下,读取,会阻塞 */
	println("完成")
}

有缓冲的 channel 会阻塞的例子

func test13() {
	/** 编译不通过 */
	/** 有缓冲的 channel 会阻塞的例子 */
	done := make(chan bool, 1)
	done <- true
	done <- false /** 放第二个值的时候,第一个还没被人拿走,这时候才会阻塞,根据缓冲值而定 */
	println("完成")
}

有缓冲的 channel 不会阻塞的例子

func test14() {
	/** 编译通过 */
	/** 有缓冲的 channel 不会阻塞的例子 */
	done := make(chan bool, 1)
	done <- true /** 不会阻塞在这里,等待读取 */

	println("完成")
}

有缓冲的 channel,如果在 go routine 中使用,一定要做适当的延时,否则会输出来不及,因为 main 已经跑完了,所以延时一会,等待 go routine

func test15() {
	/** 编译通过 */
	/** 有缓冲的channel 在 go routine 里面的例子 */
	done := make(chan bool, 1)

	go func() {
		/** 不会阻塞 */
		println("我可能会输出哦")
		done <- true /** 如果把这个注释,也会导致 <-done 阻塞 */
		println("我也可能会输出哦")
		<-done
		println("别注释 done<-true 哦,不然我就输出不了了")
	}()

	time.Sleep(time.Second * 1) /** 1秒延时,去掉就可能上面的都不会输出也有可以输出,routine 调度 */
	println("完成")

	/**
	 * 控制台输出:
	 *       我可能会输出哦
	 *       我也可能会输出哦
	 *       完成
	 */

	/**
	 * 结论:
	 *    有缓冲的 channel,如果在 go routine 中使用,一定要做适当的延时,否则会输出来不及,
	 *    因为 main 已经跑完了,所以延时一会,等待 go routine
	 */
}

多 channel 模式

func getMessagesChannel(msg string, delay time.Duration) <-chan string {
	c := make(chan string)
	go func() {
		for i := 1; i <= 3; i++ {
			c <- fmt.Sprintf("%s %d", msg, i)
			time.Sleep(time.Millisecond * delay) /** 仅仅起到,下一次的 c 在何时输入 */
		}
	}()
	return c
}

func test16() {
	/** 编译通过 */
	/** 复杂的演示例子 */
	/** 多 channel 模式 */
	c1 := getMessagesChannel("第一", 600)
	c2 := getMessagesChannel("第二", 500)
	c3 := getMessagesChannel("第三", 5000)

	/** 层层限制阻塞 */
	/** 这个 for 里面会造成等待输入,c1 会阻塞 c2 ,c2 阻塞 c3 */
	/** 所以它总是,先输出 c1 然后是 c2 最后是 c3 */
	for i := 1; i <= 3; i++ {
		/** 每次循环提取一轮,共三轮 */
		println(<-c1) /** 除非 c1 有输入值,否则就阻塞下面的 c2,c3 */
		println(<-c2) /** 除非 c2 有输入值,否则就阻塞下面的 c3 */
		println(<-c3) /** 除非 c3 有输入值,否则就阻塞进入下一轮循环,反复如此 */
	}

	/**
	 *  这个程序的运行结果,首轮的,第一,第二,第三 很快输出,因为
	 *  getMessagesChannel 函数的延时 在 输入值之后,在第二轮及其之后
	 *  因为下一个 c3 要等到 5 秒后才能输入,所以会阻塞第二轮循环的开始 5 秒,如此反复。
	 */

	/** 修改:如果把 getMessagesChannel 里面的延时,放在输入值之前,那么 c3 总是等待 5秒 后输出 */
}

在 test16 基础修改的,复杂演示例,多 channel 的选择,延时在输入之后的情况

func test17() {
	/** 编译通过 */
	/** 在 test15 基础修改的,复杂演示例子 */
	/** 多 channel 的选择,延时在输入之后的情况 */
	c1 := getMessagesChannel("第一", 600)
	c2 := getMessagesChannel("第二", 500)
	c3 := getMessagesChannel("第三", 5000)

	/** 3x3 次循环,是 9 */
	/** select 总是会把最先完成输入的channel输出,而且,互不限制 */
	/** c1,c2,c3 每两个互不限制 */
	for i := 1; i <= 9; i++ {
		select {
		case msg := <-c1:
			println(msg)
		case msg := <-c2:
			println(msg)
		case msg := <-c3:
			println(msg)
		}
	}

	/**
	 * 这个程序的运行结果:
	 *    第二 1,第三 1,第一 1,第二 2,第一 2,第二 3,第一 3,第三 2,第三 3
	 */

	/** 分析:前3次输出,“第一”,“第二”,“第三”,都有,而且
	 *  是随机顺序输出,因为协程的调度,第 4,5,6 次,由于“第二”只延时 500ms,
	 *  比 600ms 和 5000ms 都要小,那么它先输出,然后是“第一”,此时“第三”还不能输出,
	 *  因为它还在等 5 秒。此时已经输出 5 次,再过 500ms,"第三"的 5 秒还没走完,所以继续输出"第一",
	 *  再过 100ms,500+100=600,"第二"也再完成了一次,那么输出。至此,"第一"和"第二"已经
	 *  把管道的 3 个值全部输出,9-7 = 2,剩下两个是 "第三"。此时,距离首次的 5000ms 完成,
	 *  还有,500-600-600 = 3800ms,达到后,"第三" 将输出,再过 5 秒,最后一次"第三输出"
	 */
}
时间: 2024-10-13 01:34:59

[Go] 通过 17 个简短代码片段,切底弄懂 channel 基础的相关文章

[转]Android适配器之ArrayAdapter、SimpleAdapter和BaseAdapter的简单用法与有用代码片段

收藏ArrayAdapter.SimpleAdapter和BaseAdapter的一些简短代码片段,希望用时方便想起其用法. 1.ArrayAdapter 只可以简单的显示一行文本 代码片段: [java] view plaincopy ArrayAdapter<String> adapter = new ArrayAdapter<String>( this, R.layout.item,//只能有一个定义了id的TextView data);//data既可以是数组,也可以是Li

Android适配器之ArrayAdapter、SimpleAdapter和BaseAdapter的简单用法与有用代码片段(转)

摘自:http://blog.csdn.net/shakespeare001/article/details/7926783 收藏ArrayAdapter.SimpleAdapter和BaseAdapter的一些简短代码片段,希望用时方便想起其用法. 1.ArrayAdapter 只可以简单的显示一行文本 代码片段: [java] view plaincopy ArrayAdapter<String> adapter = new ArrayAdapter<String>( this

微信小程序代码片段

微信小程序代码片段是一种可分享的小项目,可用于分享小程序和小游戏的开发经验.展示组件和 API 的使用.复现开发问题等等.分享代码片段会得到一个链接,所有拥有此分享链接的人可以在工具中导入此代码片段.如果网页可点击的链接指向的是分享链接,那么点击链接也会自动打开工具进入代码片段导入页. 创建代码片段 在工具选择项目的界面中,右侧可以选择代码片段页卡,查看所有本地代码片段,在右下角可以点击创建代码片段. 创建代码片段需要填入代码片段名称.本地存放目录.AppID 不是必填项,如果需要演示依赖 Ap

30+有用的CSS代码片段

在一篇文章中收集所有的CSS代码片段几乎是不可能的事情,但是我们这里列出了一些相对于其他的更有用的代码片段,不要被这些代码的长度所吓到,因为它们都很容易实现,并且具有良好的文档.除了那些解决常见的恼人的问题外,也包含了一些解决新问题的新技术. 1. 垂直对齐 如果你之前遇到过这个问题,你就应该知道它是多么的烦人,幸运的是,现在你可以使用CSS3变换来解决这个问题: .vc{ position: relative; top: 50%; -webkit-transform: translateY(-

【转】30+有用的CSS代码片段

来自:WEB资源网 链接:http://webres.wang/31-css-code-snippets-to-make-you-a-better-coder/ 原文:http://www.designyourway.net/blog/resources/31-css-code-snippets-to-make-you-a-better-coder/ 在一篇文章中收集所有的CSS代码片段几乎是不可能的事情,但是我们这里列出了一些相对于其他的更有用的代码片段,不要被这些代码的长度所吓到,因为它们都

solr分布式索引【实战一、分片配置读取:工具类configUtil.java,读取配置代码片段,配置实例】

1 private static Properties prop = new Properties(); 2 3 private static String confFilePath = "conf" + File.separator + "config.properties";// 配置文件目录 4 static { 5 // 加载properties 6 InputStream is = null; 7 InputStreamReader isr = null;

100个直接可以拿来用的JavaScript实用功能代码片段

把平时网站上常用的一些实用功能代码片段通通收集起来,方面网友们学习使用,利用好的话可以加快网友们的开发速度,提高工作效率. 目录如下: 1.原生JavaScript实现字符串长度截取2.原生JavaScript获取域名主机3.原生JavaScript清除空格4.原生JavaScript替换全部5.原生JavaScript转义html标签6.原生JavaScript还原html标签7.原生JavaScript时间日期格式转换8.原生JavaScript判断是否为数字类型9.原生JavaScript

sublime text 2自定义代码片段

本文引用   http://www.blogjava.net/Hafeyang/archive/2012/08/17/how_to_create_code_snippet_in_subline_text_2.html 对于前端工程师来讲,写一个html页面的基本结构是体力活,每次去拷贝一个也麻烦,sublime text 2 提供了一个很好的复用代码片段.下面介绍一下创建一个html5的代码片段的过程. 在菜单上点击Tools -> New Snippet,(工具->代码段)会新建一个xml文

收集的35个 jQuery 小技巧/代码片段,可以帮你快速开发.

1. 禁止右键点击 $(document).ready(function(){     $(document).bind("contextmenu",function(e){         return false;     }); }); 2. 隐藏搜索文本框文字 Hide when clicked in the search field, the value.(example can be found below in the comment fields) $(document