Go--关于 goroutine、channel

goroutine

协程是一种轻量化的线程,由Go编译器进行优化。

Go协程具有以下特点:

  • 有独立的栈空间
  • 共享程序堆中的空间
  • 调度由用户控制

如果主线程main函数(主 goroutine或者main goroutine)返回或者退出时,即使所有协程(goroutine)还没执行完毕,也会退出。当然,协程可以在主线程未退出之前自己执行完毕,并退出。


  • 主线程是一个物理线程,直接作用在cpu上。是重量级的,非常耗费cpu资源。
  • 协程从主线程开启的,是轻量级的线程,是逻辑态的。对资源要求相对较小。
  • Golang可以开启成千上万个协程。这是Golang的并发优势。

MPG模式


  • Go1.8后,默认让程序运行在多个核上,可以不用设置了
  • Go1.8前,还是要设置一下,可以更高效的利益cpu
	numsCPU :=runtime.NumCPU()  //获取系统CPU数
runtime.GOMAXPROCS(numsCPU) //设置运行的CPU数目

channel

在此之前,先说明一种实现同步的方式:加锁(注意这里说的指互斥锁)

需求:计算n!:

var lock sync.Mutex //使用全局变量加锁

func testInput(n int)  {
res := 1
for i := 1; i <= n; i++ {
res *= i
}
lock.Lock()
myMap[n] = uint64(res)
lock.Unlock()
} func main() {
for i := 1; i <= 50; i++ {
go testInput(i)
} time.Sleep(time.Second *5) //不等待会提前结束计算,未计算的线程将被退出 lock.Lock()
for i,v := range myMap {
fmt.Println("map[",i,"]=",v)
}
lock.Unlock()
}

通过加互斥锁(同步锁)的方式,并发进行运算、添加,但是这种方式也有缺点:

  • 前面使用全局变量加锁同步来解决goroutine的通讯,但不完美
  • 主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置 5 秒,仅仅是估算。
  • 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine 处于工作状态,这时也会随主线程的退出而销毁。
  • 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。

还可以用channel来解决:

func add(s []int , c chan int)  {
sum := 0
for _, v := range s {
sum += v
fmt.Println(v)
}
c <- sum
} func main() {
c := make(chan int)
s :=[]int{2,5,9,23,7,3,4}
go add(s[:len(s)/2 ] ,c) //写channel操作会阻塞,直到读channel操作执行 go add( s[len(s)/2:] ,c) x ,y:= <-c ,<-c //随机并发
fmt.Println(x ,y ,x+y)
}

信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。

ch <- v    // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。

(“箭头”就是数据流的方向。)

和映射与切片一样,信道在使用前必须创建:

ch := make(chan int)

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。

  • channel 是线程安全的;

  • channel 本质是队列,遵循先进先出;

  • channel 中只能存放指定的数据类型;

  • channel 的数据放满后,就不能再放入;

  • 如果从channel 取出数据后,可以继续放入;

  • 在没有使用协程的情况下,如果channel 数据取完了,再取,就会报deadlock。


还可以放进任意类型(interface{})的数据:

package main

import "fmt"

type Student struct {
Name string `json:"name"` // 是 ` ` (tab键上的~按键) ,不是 ' '
Sex string `json:"sex"`
} func main() {
allChan := make(chan interface{},5) stu1 := Student{Name: "lili",Sex: "f"}
stu2 := Student{Name: "chang",Sex: "m"}
stu3 := Student{Name: "ling",Sex: "m"} allChan <- stu1
allChan <- 10
allChan <- stu2
allChan <- 99.5
allChan <- stu3 <- allChan
<- allChan
stuRes := <- allChan
fmt.Println(stuRes) //读取结构体类型数据字段,需要先进行类型断言
stu := stuRes.(Student)
fmt.Println(stu.Name)
fmt.Println(stu.Sex)
}

由于channel是interface{}类型,所以使用的时候,都需要先进行类型断言。

	allChan <- myMap
<- allChan
<- allChan
stuMap := <- allChan
stus := stuMap.(map[int]Student)
fmt.Println(stus)
fmt.Println(stus[0])
fmt.Println(stus[1])

	allChan <- stu1
allChan <- 10
allChan <- stu2
allChan <- 99.5
allChan <- stu3 <- allChan
n := <- allChan
n += 1 //报错

不使用类型断言,直接使用将会报错。因为编译器并不认识此类型,需要经过类型断言进行确认。


channel的关闭

channel关闭使用 close(chan),关闭channel。

关闭后不能再向channel发送数据,只能从channel读取数据。

在上面的例子中的<-allChan前加入以下代码:

close(allChan)


channel的遍历

遍历channel之前需要关闭channel,否则会报错(deadlock)。

关闭channel后,即可正常进行遍历channel,知道遍历完成,退出遍历。

	close(allChan)
for v := range allChan {
fmt.Println(v)
}

只读、只写 channel

  • 只读channel:(例如)
	var chan1 <-chan int
  • 只写channel:(例如)
	var chan2 chan<- int

案例:

func send(ch chan<- int,exit chan  struct{})  {
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("输入",i)
}
close(ch)
var a struct{}
exit <- a
} func get(ch <-chan int,exit chan struct{}) {
for i := 0; i < 10; i++ {
v,ok := <-ch
if !ok {
break
}
fmt.Println("输出",v)
}
var a struct{}
exit <- a
} func main() {
ch := make(chan int ,10) //双向通道
exitChan := make(chan struct{} ,2)
go send(ch,exitChan)
go get(ch,exitChan)
for {
if len(exitChan) == 2 {
break
}
}
}


可以使用for+select语句防止阻塞:

func main() {
ch := make(chan int ,10)
for i := 0; i < 5; i++ {
ch <- i
} ch2 := make(chan float64 ,10)
for i := 0; i < 5; i++ {
ch2 <- rand.Float64()
}
label:
for {
select {
case n:= <-ch:
fmt.Println(n)
time.Sleep(time.Second)
case m:=<-ch2:
fmt.Println(m)
time.Sleep(time.Second)
default:
fmt.Println("没了")
time.Sleep(time.Second)
//return 直接结束退出程序运行
break label //中断指定的 for 循环
}
}
}


使用defer+recover解决运行时协程中抛出的panic,保证程序继续运行:

func wrong()  {
defer func(){
if e := recover() ; e != nil {
fmt.Print("func wrong()计算错误,")
fmt.Println("异常",e)
}
}() num := 0
num1 := 100
num = num1 /num
fmt.Println("func wrong()计算正确",num)
} func right() {
defer func(){
if e := recover() ; e != nil {
fmt.Println(e)
}
}() num := 10
num1 := 100
num = num1 /num
fmt.Println("func right() 计算正确",num)
} func main() {
go wrong()
go right()
for i := 0; i < 5; i++ {
time.Sleep(time.Second)
i++
}
}

关于recover:

内建函数recover允许程序管理恐慌过程中的Go程。在defer的函数中,执行recover调用会取回传至panic调用的错误值,恢复正常执行,停止恐慌过程。若recover在defer的函数之外被调用,它将不会停止恐慌过程序列。在此情况下,或当该Go程不在恐慌过程中时,或提供给panic的实参为nil时,recover就会返回nil。


Go--关于 goroutine、channel的更多相关文章

  1. Go语言学习笔记(七)杀手锏 Goroutine + Channel

    加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 Goroutine Go语言的主要的功能在于令人简易使用的并行设计,这个方法叫做Goroutine,通过Gorou ...

  2. 使用goroutine+channel和java多线程+queue队列的方式开发各有什么优缺点?

    我感觉很多项目使用java或者c的多线程库+线程安全的queue数据结构基本上可以实现goroutine+channel开发能达到的需求,所以请问一下为什么说golang更适合并发服务端的开发呢?使用 ...

  3. 【转】关于 Goroutine Channel Select 的用法和理解

    原文:https://blog.csdn.net/jfkidear/article/details/88661693 ----------------------------------------- ...

  4. 一个Golang例子:for + goroutine + channel

    Rob Pike 在 Google I/O 2012 - Go Concurrency Patterns 里演示了一个例子(daisy chain). 视频地址:https://www.youtube ...

  5. golang并发编程goroutine+channel(一)

    go语言的设计初衷除了在不影响程序性能的情况下减少复杂度,另一个目的是在当今互联网大量运算下,如何让程序的并发性能和代码可读性达到极致.go语言的并发关键词 "go" go dos ...

  6. go14--并发concurrency,Goroutine ,channel

    package main /** 并发concurrency 很多人都是冲着 Go 大肆宣扬的高并发而忍不住跃跃欲试,但其实从 源码的解析来看,goroutine 只是由官方实现的超级“线程池”而已. ...

  7. goroutine,channel

    Go语言中有个概念叫做goroutine, 这类似我们熟知的线程,但是更轻. 以下的程序,我们串行地去执行两次loop函数: package main import "fmt" f ...

  8. go语言之行--golang核武器goroutine调度原理、channel详解

    一.goroutine简介 goroutine是go语言中最为NB的设计,也是其魅力所在,goroutine的本质是协程,是实现并行计算的核心.goroutine使用方式非常的简单,只需使用go关键字 ...

  9. [GO语言的并发之道] Goroutine调度原理&Channel详解

    并发(并行),一直以来都是一个编程语言里的核心主题之一,也是被开发者关注最多的话题:Go语言作为一个出道以来就自带 『高并发』光环的富二代编程语言,它的并发(并行)编程肯定是值得开发者去探究的,而Go ...

  10. Go学习——go+channel实战(转)

    转载:http://studygolang.com/articles/2423 背景 在最近开发的项目中,后端需要编写许多提供HTTP接口的API,另外技术选型相对宽松,因此选择Golang + Be ...

随机推荐

  1. Java基础——克隆

    1.克隆 假设有一个对象object1,在某处又需要一个跟object1一样的实例object2,这两个对象是绝对独立的,不会因为某一个修改另一个随之改变,这样,我们不能直接将对象objec1t的引用 ...

  2. Spring学习(三)--Spring的IOC

    1.BeanFactory和FactoryBean BeanFactory是一个接口类,定义了IOC容器最基本的形式,提供了IOC容器所应该遵守的基本服务契约. FactoryBean是一个能产生或者 ...

  3. Java 集合 | 红黑树 | 前置知识

    一.前言 0tnv1e.png 为啥要学红黑树吖? 因为笔者最近在赶项目的时候,不忘抽出时间来复习 Java 基础知识,现在准备看集合的源码啦啦.听闻,HashMap 在 jdk 1.8 的时候,底层 ...

  4. 【Python】类

    初探类 类定义与函数定义( def语句 )一样必须被执行才会起作用 调用 x.f() 其实就相当于 MyClass.f(x) 补充说明 数据属性会覆盖掉具有相同名称的方法属性 命名方法 方法名称使用大 ...

  5. 073 01 Android 零基础入门 01 Java基础语法 09 综合案例-数组移位 05 综合案例-数组移位-主方法功能1和2的实现

    073 01 Android 零基础入门 01 Java基础语法 09 综合案例-数组移位 05 综合案例-数组移位-主方法功能1和2的实现 本文知识点:综合案例-数组移位-主方法功能1和2的实现 说 ...

  6. C++ | 继承(基类,父类,超类),(派生类,子类)

    转载:https://blog.csdn.net/Sherlock_Homles/article/details/82927515 文章参考:https://blog.csdn.net/war1111 ...

  7. Combine 框架,从0到1 —— 5.Combine 常用操作符

    本文首发于 Ficow Shen's Blog,原文地址: Combine 框架,从0到1 -- 5.Combine 常用操作符. 内容概览 前言 print breakpoint handleEve ...

  8. NOIP提高组2013 D2T3 【华容道】

    某王  老师给我们考了一场noip2013的真题...心态爆炸! 题目大意: 有一个n*m的棋盘,每个格子上都有一个棋子,有些格子上的棋子能够移动(可移动的棋子是固定的),棋盘中有一个格子是空的,仍何 ...

  9. 重装Windows系统 入门详解 - 基础教程

    重装Windows系统 入门详解 - 基础教程 JERRY_Z. ~ 2020 / 10 / 13 转载请注明出处!️ 目录 重装Windows系统 入门详解 - 基础教程 一.说明 二.具体步骤 ( ...

  10. Prometheus入门教程(二):Prometheus + Grafana实现可视化、告警

    文章首发于[陈树义]公众号,点击跳转到原文:https://mp.weixin.qq.com/s/56S290p4j9KROB5uGRcGkQ Prometheus UI 提供了快速验证 PromQL ...