[系列] Go - chan 通道
<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>概述</li><li>声明 chan</li><li>写入 chan</li><li>读取 chan</li><li>关闭 chan</li><li>示例</li><li>推荐阅读</li></ul></div><p></p><h2 id="概述">概述</h2>
<p>原来分享基础语法的时候,还未分享过 chan 通道,这次把它补上。</p>
<p>chan 可以理解为队列,遵循先进先出的规则。</p>
<p>在说 chan 之前,咱们先说一下 go 关键字。</p>
<p>在 go 关键字后面加一个函数,就可以创建一个线程,函数可以为已经写好的函数,也可以是匿名函数。</p>
<p>举个例子:</p>
<pre><code>func main() {
fmt.Println("main start")
go func() {
fmt.Println("goroutine")
}()
fmt.Println("main end")
}
</code></pre>
<p>输出:</p>
<pre><code>main start
main end
</code></pre>
<p>为什么没有输出 goroutine ?</p>
<p>首先,我们清楚 Go 语言的线程是并发机制,不是并行机制。</p>
<p>那么,什么是并发,什么是并行?</p>
<p>并发是不同的代码块交替执行,也就是交替可以做不同的事情。</p>
<p>并行是不同的代码块同时执行,也就是同时可以做不同的事情。</p>
<p>举个生活化场景的例子:</p>
<p>你正在家看书,忽然电话来了,然后你接电话,通话完成后继续看书,这就是并发,看书和接电话交替做。</p>
<p>如果电话来了,你一边看书一遍接电话,这就是并行,看书和接电话一起做。</p>
<p>说回上面的例子,为什么没有输出 goroutine ?</p>
<p>main 函数是一个主线程,是因为主线程执行太快了,子线程还没来得及执行,所以看不到输出。</p>
<p>现在让主线程休眠 1 秒钟,再试试。</p>
<pre><code>func main() {
fmt.Println("main start")
go func() {
fmt.Println("goroutine")
}()
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
</code></pre>
<p>输出:</p>
<pre><code>main start
goroutine
main end
</code></pre>
<p>这就对了。</p>
<p>接下来,看看如何使用 chan 。</p>
<h2 id="声明-chan">声明 chan</h2>
<pre><code>// 声明不带缓冲的通道
ch1 := make(chan string)
// 声明带10个缓冲的通道
ch2 := make(chan string, 10)
// 声明只读通道
ch3 := make(<-chan string)
// 声明只写通道
ch4 := make(chan<- string)
</code></pre>
<p>注意:</p>
<p>不带缓冲的通道,进和出都会阻塞。</p>
<p>带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞。</p>
<h2 id="写入-chan">写入 chan</h2>
<pre><code>ch1 := make(chan string, 10)
ch1 <- "a"
</code></pre>
<h2 id="读取-chan">读取 chan</h2>
<pre><code>val, ok := <- ch1
// 或
val := <- ch1
</code></pre>
<h2 id="关闭-chan">关闭 chan</h2>
<pre><code>close(chan)
</code></pre>
<p>注意:</p>
<ul>
<li>close 以后不能再写入,写入会出现 panic</li>
<li>重复 close 会出现 panic</li>
<li>只读的 chan 不能 close</li>
<li>close 以后还可以读取数据</li>
</ul>
<h2 id="示例">示例</h2>
<pre><code>func main() {
fmt.Println("main start")
ch := make(chan string)
ch <- "a" // 入 chan
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
fmt.Println("main end")
}
</code></pre>
<p>输出:</p>
<pre><code>main start
fatal error: all goroutines are asleep - deadlock!
</code></pre>
<p>What ? 这是为啥,刚开始就出师不利呀?</p>
<p>因为,定义的是一个无缓冲的 chan,赋值后就陷入了阻塞。</p>
<p>怎么解决它?</p>
<p>声明一个有缓冲的 chan。</p>
<pre><code>func main() {
fmt.Println("main start")
ch := make(chan string, 1)
ch <- "a" // 入 chan
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
fmt.Println("main end")
}
</code></pre>
<p>输出:</p>
<pre><code>main start
main end
</code></pre>
<p>为啥没有输出 a , 和前面一样,主线程执行太快了,加个休眠 1 秒钟,再试试。</p>
<pre><code>func main() {
fmt.Println("main start")
ch := make(chan string, 1)
ch <- "a" // 入 chan
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
</code></pre>
<p>输出:</p>
<pre><code>main start
a
main end
</code></pre>
<p>这就对了。</p>
<p>再看一个例子:</p>
<pre><code>func main() {
fmt.Println("main start")
ch := make(chan string)
go func() {
ch <- "a" // 入 chan
}()
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
</code></pre>
<p>输出:</p>
<pre><code>main start
a
main end
</code></pre>
<p>再看一个例子:</p>
<pre><code>func producer(ch chan string) {
fmt.Println("producer start")
ch <- "a"
ch <- "b"
ch <- "c"
ch <- "d"
fmt.Println("producer end")
}
func main() {
fmt.Println("main start")
ch := make(chan string, 3)
go producer(ch)
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
</code></pre>
<p>输出:</p>
<pre><code>main start
producer start
main end
</code></pre>
<p>带缓冲的通道,如果长度等于缓冲长度时,再进就会阻塞。</p>
<p>再看一个例子:</p>
<pre><code>func producer(ch chan string) {
fmt.Println("producer start")
ch <- "a"
ch <- "b"
ch <- "c"
ch <- "d"
fmt.Println("producer end")
}
func customer(ch chan string) {
for {
msg := <- ch
fmt.Println(msg)
}
}
func main() {
fmt.Println("main start")
ch := make(chan string, 3)
go producer(ch)
go customer(ch)
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
</code></pre>
<p>输出:</p>
<pre><code>main start
producer start
producer end
a
b
c
d
main end
</code></pre>
<p>就到这吧。</p>
<h2 id="推荐阅读">推荐阅读</h2>
<p><strong>gRPC</strong></p>
<ul>
<li>Go gRPC Hello World</li>
</ul>
<p><strong>Gin 框架</strong></p>
<ul>
<li>Gin 框架 - 自定义错误处理</li>
<li>Gin 框架 - 数据绑定和验证</li>
<li>Gin 框架 - 使用 Logrus 日志记录</li>
<li>Gin 框架 - 安装和路由配置</li>
</ul>
<p><strong>基础篇</strong></p>
<ul>
<li>Go - chan 通道</li>
<li>Go - 函数</li>
<li>Go - 循环</li>
<li>Go - Map 集合</li>
<li>Go - Struct 结构体</li>
<li>Go - Slice 切片</li>
<li>Go - 数组</li>
<li>Go - 变量声明</li>
<li>Go - 环境安装</li>
</ul>
<p>本文欢迎转发,转发请注明作者和出处,谢谢!</p>
</div>
<div id="MySignature" role="contentinfo">
<p style="text-align: center"><img src="https://img2020.cnblogs.com/blog/389840/202008/389840-20200822143814170-1389230174.png"></p>
<p style="border: #e7e7e7 1px solid; padding: 10px 10px; font-size: 12px; background-color: whitesmoke"><span style="margin-left: -14px">作者:新亮笔记(关注公众号,可申请添加微信好友)</span>
<br>
<span style="margin-left: 10px">出处:https://www.cnblogs.com/xinliangcoder</span>
<br>
<span style="margin-left: 10px; color: black">本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。</span>
</p><br><br>
来源:https://www.cnblogs.com/xinliangcoder/p/11286801.html
頁:
[1]