Chapter016 goroutine协程 和 channel管道

上一章:Chapter015 golang单元测试

一、进程和线程

1、百度百科

  1. 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
  2. 线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

2、举个例子

  1. 打开一个迅雷应用,那么迅雷应用就有一个进程,如果选择下载多个文件,那么很多个下载任务就是一个个的线程。(有些程序还可以多进程)
进程1
线程1
线程2
线程3
线程4
二、并发和并行(go程序的)

1、简述

  1. 多线程程序在单核
  2. 多线程程序在多核

2、并发的特点

  1. 多个任务作用在一个cpu上
  2. 从微观的角度来看,在一个时间点上,其实只有一个任务在执行
任务1
CPU1
任务2

3、并行的特点

  1. 多个任务作用在多个cpu上
  2. 从微观的角度来看,在一个时间点上,有多个任务在同时执行
任务1
CPU1
任务2
CPU2
三、go的协程和go的主线程

1、简单说明

  1. Go主线程(有程序员直接称为线程/也可以理解为进程)
  2. 一个Go线程上可以起多个协程,即可以理解为:协程是轻量级的线程

2、GO协程的特点

  1. 有独立的栈空间
  2. 共享程序堆空间
  3. 调度由用户控制
  4. 协程是轻量级的线程
四、gorountine 快速入门

1、编写一个程序,完成如下功能:

  1. 在主线程中(在进程中),开启一个gorountine ,该协程每隔1s输出一个“hello world”
  2. 在主线程中也每隔一秒输出“hello,golang”,输出10次后,退出程序
  3. 要求主线程和gorountine同时执行
  4. 要求主线程和协程执行流程图
程序开始
开启协程
协程在这里for循环
主线程在这里for循环
主线程结束程序退出
  1. 注意事项
    1))如果主线程退出了,则协程即使还没有执行完毕,也会退出
    2))当然协程也可以在主线程没有结束之前就自己结束了,比如完成了自己的任务
  2. 小结
    1))主线程是一个物理线程直接作用在CPU上,是重量级的,非常消耗CPU资源
    2))协程是从主线程开启的,是轻量级的线程,是逻辑态的,对资源消耗相对较小
package main

import (
	"fmt"
	_ "fmt"
	"strconv"
	"time"
)

//1) 在主线程中(在进程中),开启一个gorountine ,该协程每隔1s输出一个“hello world”
//2) 在主线程中也每隔一秒输出“hello,golang”,输出10次后,退出程序
//3) 要求主线程和gorountine同时执行
//4) 要求主线程和协程执行流程图

func test01()  {
	for i:= 1; i<=4;i++{
		fmt.Println("test01(): hello world",strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

func test02()  {
	for i:= 1; i<=4;i++{
		fmt.Println("test02(): hello world",strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

func main()  {
	test01()
	go test02() //开启协程 ,输出test02穿插
	for i:= 1; i<=4;i++{
		fmt.Println("main(): hello golang",strconv.Itoa(i))
		time.Sleep(time.Second)
	}

}

运行结果:
在这里插入图片描述

五、goroutine的调度模型 MPG

1、简单介绍

  1. M:操作系统的主线程(是物理线程)
  2. P:协程执行需要的上下文
  3. G:协程

2、设置Golang运行的cpu数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

六、channel 管道引例

1、main.go

package main

import (
	"fmt"
)

//全局变量myMap
var  (
	myMap = make(map[int]int,10)
) 

//计算n的阶乘保存到myMap
func test(n int)  {	
	res :=1
	for i :=1;i<=n;i++{
		res *=i
	}
	myMap[n] = res
}

func main()  {
	//开启协程完成任务
	for i:=1;i<=200;i++{
		go test(i)
	}
	//输出结果
	for i,v := range myMap{
		fmt.Printf("第【%v】= %v\n",i,v)
	}
}

代码这样写看似没有问题,但是没有输出结果或者不全,原因是主线程完成任务后退出程序,然而协程还没有完成

2、main.go

package main

import (
	"fmt"
)

//全局变量myMap
var  (
	myMap = make(map[int]int,10)
) 

//计算n的阶乘保存到myMap
func test(n int)  {	
	res :=1
	for i :=1;i<=n;i++{
		res *=i
	}
	myMap[n] = res
}

func main()  {
	//开启协程完成任务
	for i:=1;i<=200;i++{
		go test(i)
	}
	//*******************************************************添加
	time.Sleep(10*time.Second)
	//输出结果
	for i,v := range myMap{
		fmt.Printf("第【%v】= %v\n",i,v)
	}
}

如果添加*标明的time.Sleep(),会报错没有保护机制,原因很简单,有200个协程同时操作同一块地址

主线程
协程1
协程2
协程3
...协程200
map空间

在这里插入图片描述

tips:在运行时,加上参数-race可以知道是否存在资源竞争

go build -race main.go

在这里插入图片描述
3、问题解决方法
(1)全局变量加锁同步

主线程
协程1
协程2
协程3
...协程200
map空间加锁
队列

加入互斥锁:
在这里插入图片描述main.go

package main

import (
	"fmt"
	"sync"
	"time"
)

//全局变量myMap
var  (
	myMap = make(map[int]int,10)
	//lock为全局互斥锁
	//sync 为一个包 同步
	//Mutex 结构体 互斥
	lock sync.Mutex
)

//计算n的阶乘保存到myMap
func test(n int)  {
	res :=1
	for i :=1;i<=n;i++{
		res *=i
	}
	//加锁
	lock.Lock()
	myMap[n] = res
	//解锁
	lock.Unlock()
}

func main()  {
	//开启协程完成任务
	for i:=1;i<=200;i++{
		go test(i)
	}
	time.Sleep(time.Second*10)
	//输出结果
	//加锁
	lock.Lock()
	for i,v := range myMap{
		fmt.Printf("第【%v】= %v\n",i,v)
	}
	//解锁
	lock.Unlock()
}

运行结果:
在这里插入图片描述
为什么还要进行如下加锁呢?
在这里插入图片描述
原因很简单,因为我们从程序设计上知道10s可以完成所有协程,单数主线程不知道,因此底层仍然可能出现资源争夺,因此还是要加上互斥锁解决问题
(2)channel管道

七、channel 的基本介绍

1、channel本质是一个数据结构——队列
2、先进先出
3、线程安全、多goroutine访问时,不需要加锁,就是说channel本质是线程安全的

主线程
协程1
协程2
协程3
...协程200
channel管道队列

4、channel是有类型的,一个string的channel只能存放string类型数据

八、channel 的使用

1、声明定义
(1)管道存放基本数据类型

var 变量名 chan 数据类型

(2)管道存放map

var mapChan chan map[int]string (mapChan用于存放map[int]string类型)

(3)管道存放结构体

var perChan chan Person

(4)管道存放结构体指针

var perChan2 chan *Person

2、使用说明
(1)chan是引用类型

package main

import "fmt"

func main()  {
	var intChan chan int
	intChan = make(chan int,3)

	fmt.Printf("%v",intChan)
}

在这里插入图片描述
(2)chan必须初始化才能写入数据,即make后使用
(3)管道是有类型的
3、初始化以及使用

package main

import "fmt"

func main()  {
	var intChan chan int
	intChan = make(chan int,3)

	fmt.Printf("%v\n",intChan)

	//写入数据,最多3个,不能超过其容量
	intChan<- 10
	num := 211
	intChan<- num

	//查看管道长度和容量
	fmt.Printf("len:%v cap:%v",len(intChan),cap(intChan))

	//从管道中读取数据,长度变少,容量不变
	//在没有使用协程的情况下,如果我们一直取数据,再取就会报告deadlock
	var num2 int
	num2 = <-intChan
	fmt.Print(num2)
	
}

tips

  1. 从管道中读取数据,长度变少,容量不变
  2. 在没有使用协程的情况下,如果我们一直取数据,再取就会报告deadlock
  3. 管道精髓在于边取边放
  4. 扔掉管道内数据(假设管道名字叫做 intChan )
<-intChan

4、channel的关闭
在这里插入图片描述
当管道关闭后没有办法写入,但是可以读
错误maingo

package main

import "fmt"

func main()  {
	intChan := make(chan int,3)
	intChan <- 100
	intChan <- 200
	close(intChan)
	intChan <-300
	fmt.Println("ok~")
}

在这里插入图片描述
正确main.go

package main

import "fmt"

func main()  {
	intChan := make(chan int,3)
	intChan <- 100
	intChan <- 200
	close(intChan)
	fmt.Println("ok~")
}

在这里插入图片描述
5、channel的遍历
(1)支持for-range的遍历

  1. 在遍历时,如果channel没有关闭,则会出现deadlock的现象
  2. 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出程序
    attention:关闭管道!!!
package main

import "fmt"

func main()  {
	//遍历管道
	intChan2 :=make(chan int,100)
	for i:= 0;i<100;i++{
		intChan2<- i*2
	}
	//不关闭管道会发生死锁
	close(intChan2)
	for v := range intChan2{
		fmt.Printf("%v\n",v)
	}
}

在这里插入图片描述
6、综合案例1
(1)描述:
一边写入管道,一边读管道数据(由于太快,所以可以适当延时)
(2)流程图
在这里插入图片描述

package main

import (
	"fmt"
	"time"
)

func WriteData(intChan chan int)  {
	for i:=1;i<=10;i++{
		intChan<-i
		fmt.Println("写入数据",i)
		time.Sleep(time.Second)
	}
	close(intChan)
}

func ReadData(intChan chan int, exitChan chan bool)  {
	for{
		v,ok := <-intChan
		if !ok{
			break
		}
		fmt.Printf("readData 读到数据%v\n",v)
		time.Sleep(time.Second)

	}
	exitChan <- true
	close(exitChan)
}

func main()  {
	intChan :=make(chan int,10)
	exitChan :=make(chan bool,10)
	go WriteData(intChan)
	go ReadData(intChan,exitChan)
	for {
		_,ok := <- exitChan
		if !ok{
			break
		}
	}
}

在这里插入图片描述

九、gorountine和channel结合

1、案例,输出8000内所有素数

package main

import "fmt"

func putNum(intChan chan  int)  {
	for i:=1;i<=8000;i++{
		intChan<-i
	}
	close(intChan)
}

func primeNum(intChan chan int,primeChan chan int,exitChan chan bool)  {
	var flag bool
	for{
		num,ok :=<-intChan
		if !ok{
			break
		}
		flag=true
		for i:=2;i<num;i++{
			if num % i == 0{
				flag = false
				break
			}
		}
		if flag{
			primeChan<-num
		}
	}
	fmt.Println("有一个协程primChan取不到数据,退出")
	exitChan<-true
}

func main()  {
	intChan := make(chan int,1000)
	primeChan :=make(chan int,2000)
	exitChan :=make(chan bool,4)

	//开启一个协程,向intChan 放入数据1-8000个数
	go putNum(intChan)
	//开启4个协程,从intChan 取出数据,并判断是否为素数
	//如果是,就放到primeChan
	for i:=0;i<4 ;i++  {
		go primeNum(intChan,primeChan,exitChan)
	}
	//主线程,进行处理
	//直接
	go func() {
		for i:=0;i<4 ;i++  {
			<-exitChan
		}
		//取出4个就可以放心关闭
		close(primeChan)
	}()
	for{
		res,ok :=<-primeChan
		if !ok{
			break
		}
		fmt.Printf("primeNum=%v\n",res)
	}
	fmt.Println("main主线程退出")
}

在这里插入图片描述

十、管道只读或者只写

1、只写

var chan2 chan<- int

2、只读

var chan3 <-chan int
十一、select解决管道阻塞

1、问题:不关管道容易阻塞
2、解决:

for{
	select{
		case v1:= <- intChan :
			fmt.Printf("从intChan读取的数据为%d\n",v)
		case v2:= <- stringChan:
			fmt.Printf("从stringChan读取的数据为%s\n",v)
		default:
			fmt.Printf("都取不到了,可以加上自己逻辑\n",v)
			break //return 
	}
}
十二、gorountine的panic捕获——panic
func test(){
	defer func(){  //defer + 匿名函数
		//捕获发生的错误
		if err:=recover();err !=nil{
			fmt.Println("test()发生错误",err)
		}
	}()
	var myMap map[int]string
	myMap[0] = "golang"
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰西啊杰西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值