原文:http://blog.csdn.net/netdxy/article/details/54564436

在用 chan 类型时,发生死锁的错误,表面上看不出什么问题

-------------------------------------------------------------------------------------------------------

首先我们来看线程,在golang里面也叫goroutine

在读这篇文章之前,我们需要了解一下并发与并行。golang的线程是一种并发机制,而不是并行。它们之间的区别大家可以上网搜一下,网上有很多的介绍。

下面我们先来看一个例子吧

import(
"fmt"
) funcmain(){ go fmt.Println("1")
fmt.Println("2")
}

 在golang里面,使用Go这个关键字,后面再跟上一个函数就可以创建一个线程。后面的这个函数可以是已经写好的函数,也可以是一个匿名函数

funcmain(){

    var i=3

    go func(a int) {
fmt.Println(a)
fmt.Println("1")
}(i)
fmt.Println("2") }

上面的代码就创建了一个匿名函数,并且还传入了一个参数i,下面括号里的i是实参,a是形参。

那么上面的代码能按照我们预想的打印1、2、3吗?告诉你们吧,不能,程序只能打印出2。下面我把正确的代码贴出来吧

import(

    "fmt"
"time"
)
funcmain(){ var i = 3
go func(a int) {
fmt.Println(a)
fmt.Println("1")
}(i)
fmt.Println("2")
time.Sleep(1 * time.Second)
}

我只是在最后加了一行让主线程休眠一秒的代码,程序就会依次打印出2、3、1。

那为什么会这样呢?因为程序会优先执行主线程,主线程执行完成后,程序会立即退出,没有多余的时间去执行子线程。如果在程序的最后让主线程休眠1秒钟,那程序就会有足够的时间去执行子线程。

线程先讲到这里,下面我们来看看通道吧。

通道又叫channel,顾名思义,channel的作用就是在多线程之间传递数据的。

创建无缓冲channel

chreadandwrite :=make(chan int)

chonlyread := make(<-chan int) //创建只读channel 
chonlywrite := make(chan<- int) //创建只写channel 
下面我们来看一个例子:

ch :=make(chan int)
ch <- 1
go func() {
<-ch
fmt.Println("1")
}()
fmt.Println("2")

这段代码执行时会出现一个错误:fatal error: all goroutines are asleep - deadlock!

这个错误的意思是说线程陷入了死锁,程序无法继续往下执行。那么造成这种错误的原因是什么呢?

我们创建了一个无缓冲的channel,然后给这个channel赋值了,程序就是在赋值完成后陷入了死锁。因为我们的channel是无缓冲的,即同步的,赋值完成后来不及读取channel,程序就已经阻塞了。这里介绍一个非常重要的概念:channel的机制是先进先出,如果你给channel赋值了,那么必须要读取它的值,不然就会造成阻塞,当然这个只对无缓冲的channel有效。对于有缓冲的channel,发送方会一直阻塞直到数据被拷贝到缓冲区;如果缓冲区已满,则发送方只能在接收方取走数据后才能从阻塞状态恢复。

对于上面的例子有两种解决方案:

1、给channel增加缓冲区,然后在程序的最后让主线程休眠一秒,代码如下:

ch :=make(chan int,1)
ch <- 1
go func() {
v := <-ch
fmt.Println(v)
}()
time.Sleep(1 * time.Second)
fmt.Println("2")

 这样的话程序就会依次打印出1、2

2、把ch<-1这一行代码放到子线程代码的后面,代码如下:

ch :=make(chan int)

    go func() {
v := <-ch
fmt.Println(v)
}()
ch <- 1
fmt.Println("2")

  

这里就不用让主线程休眠了,因为channel在主线程中被赋值后,主线程就会阻塞,直到channel的值在子线程中被取出。

最后我们看一个生产者和消费者的例子:

import (

    "fmt"
"time"
)
func produce(p chan<- int) {
for i := 0; i < 10; i++ {
p <- i
fmt.Println("send:", i)
}
}
func consumer(c <-chan int) {
for i := 0; i < 10; i++ {
v := <-c
fmt.Println("receive:", v)
}
}
func main() {
ch := make(chan int)
go produce(ch)
go consumer(ch)
time.Sleep(1 * time.Second)
}

  

在这段代码中,因为channel是没有缓冲的,所以当生产者给channel赋值后,生产者这个线程会阻塞,直到消费者线程将channel中的数据取出。消费者第一次将数据取出后,进行下一次循环时,消费者的线程也会阻塞,因为生产者还没有将数据存入,这时程序会去执行生产者的线程。程序就这样在消费者和生产者两个线程间不断切换,直到循环结束。

下面我们再看一个带缓冲的例子:

import (

    "fmt"
"time"
)
func produce(p chan<- int) {
for i := 0; i < 10; i++ {
p <- i
fmt.Println("send:", i)
}
}
func consumer(c <-chan int) {
for i := 0; i < 10; i++ {
v := <-c
fmt.Println("receive:", v)
}
}
func main() {
ch := make(chan int, 10)
go produce(ch)
go consumer(ch)
time.Sleep(1 * time.Second)
}

在这个程序中,缓冲区可以存储10个int类型的整数,在执行生产者线程的时候,线程就不会阻塞,一次性将10个整数存入channel,在读取的时候,也是一次性读取。

【转】Golang 关于通道 Chan 详解的更多相关文章

  1. Golang官方log包详解

    Golang官方log包详解 以下全是代码, 详解在注释中, 请从头到尾看 // Copyright 2009 The Go Authors. All rights reserved. // Use ...

  2. golang包time用法详解

    在我们编程过程中,经常会用到与时间相关的各种务需求,下面来介绍 golang 中有关时间的一些基本用法,我们从 time 的几种 type 来开始介绍. 时间可分为时间点与时间段,golang 也不例 ...

  3. Golang 之协程详解

    转自:https://www.cnblogs.com/liang1101/p/7285955.html 一.Golang 线程和协程的区别 备注:需要区分进程.线程(内核级线程).协程(用户级线程)三 ...

  4. golang(11) 反射用法详解

    原文链接:http://www.limerence2017.com/2019/10/14/golang16/ 反射是什么 反射其实就是通过变量动态获取其值和类型的一种技术,有些语言是支持反射的比如py ...

  5. 【GoLang】GoLang fmt 占位符详解

    golang 的fmt 包实现了格式化I/O函数,类似于C的 printf 和 scanf. # 定义示例类型和变量 type Human struct { Name string } var peo ...

  6. 深入Golang之sync.Pool详解

    我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池. sync.Pool ...

  7. golang --os系统包详解

    环境变量 Environ 获取所有环境变量, 返回变量列表 func Environ() []string package main import ( "fmt" "os ...

  8. golang中defer的详解 转自https://blog.csdn.net/skh2015java/article/details/77081250

    Go里的defer很有用,尤其在很多执行模块化操作时,初始化时给各个需要执行的模块传入参数,但是这些参数有些事在模块执行过程中才赋值的. 这时候有了defer就不会把代码写的很凌乱. Go的defer ...

  9. Golang flag包使用详解(一)

    概述 flag包提供了一系列解析命令行参数的功能接口 命令行语法 命令行语法主要有以下几种形式 -flag //只支持bool类型 -flag=x -flag x //只支持非bool类型 以上语法对 ...

随机推荐

  1. vue2.0 v-model指令

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. Dynamic type checking and runtime type information

    动态类型的关键是将动态对象与实际类型信息绑定. See also: Dynamic programming language and Interpreted language Dynamic type ...

  3. CE工具里自带的学习工具--第三关

    图解: 重复第5,6,7,8,9步,最终得到:

  4. CAD参数绘制直径标注(网页版)

    主要用到函数说明: _DMxDrawX::DrawDimDiametric 绘制一个直径标注.详细说明如下: 参数 说明 DOUBLE dChordPointX 在被标注的曲线上的第一个点X值 DOU ...

  5. babun

    ‎ Table of Contents 1. 环境 2. 检查/更新 3. 包管理 4. 版本管理 Git 4.1. 设置姓名邮箱(全局方式) 4.2. 添加 SSH 4.3. 链接测试 4.4. 权 ...

  6. vue中axios发送post请求,后端(@RequestParam)接不到参数

    遇到的问题描述 :axios post 请求,后端接收不到参数. 我们的接口是java,用@RequestParam来接收前端的参数 解决方案:使用qs:axios中已经包含有qs,所以无需重新安装, ...

  7. 在springBoot的控制台打印sql语句

    在springBoot+Mybatis日志显示SQL的执行情况的最简单方法就是在properties新增: properties形式 logging.level.com.eth.wallet.mapp ...

  8. SpringBoot中部署Swagger2和Swagger-UI

    1 Gradle配置在dependencies中添加以下依赖: implementation("io.springfox:springfox-swagger2:2.7.0") im ...

  9. centos6基础优化

    一.关闭SELinux功能 selinux功能太严苛,还是关闭了吧 法一:修改配置文件,永久生效 [root@web01 ~]# sed -i 's/SELINUX=enforcing/SELINUX ...

  10. FFmpeg加水印

    ffmpeg中文水印乱码两种原因 1.字符编码格式原因,中文必须是utf8编码格式的(我遇到的问题,在vs2013上写的中文,已做编码格式转码,放到centos7.2上编译运行也会出现中文乱码的问题, ...