1.1、线程和进程介绍
1.2、程序、进程和线程的关系示意图
1.3、并发和并行
1.4、go协程的主线程
1.5、goroutine(协程)的简单应用
编写程序完成以下功能:
import (
"fmt"
"time"
)
/**
* @Description:goroutine 简单应用
* @Author: guai
* @Date:2020/2/25 22:28
**/
//编写一个函数,每隔一秒输出”hello,world
func test() {
for i := 0; i < 10; i++ {
fmt.Println("test() hello,world", i)
time.Sleep(time.Second)
}
}
func main() {
//开启一个协程
go test()
for i := 0; i < 10; i++ {
fmt.Println("main() hello,golang", i)
time.Sleep(time.Second)
}
}
结果:
1.5.1、小结
1.6、goroutine的调度模型
1.6.1、MPG模式基本介绍
1.6.2、MPG模式运行的状态1
1.6.3、
1.6.4、设置golang程序运行的cpu数
package main
import (
"fmt"
"time"
)
/**
* @Description:channel管道 用于多协称之间的通信
* @Author: guai
* @Date:2020/2/25 23:49
**/
func main() {
//创建一个map保存阶乘的结果
var myMap = make(map[int]int, 10)
cal := func(n int) {
res := 1
for i := 1; i < n; i++ {
res *= i
}
//将结果放到map中
myMap[n] = res
}
//开启多协程 完成任务
for i := 0; i < 130; i++ {
go cal(i)
}
//输出结果
//防止主线程在协程运行完之前结束
time.Sleep(time.Second * 3)
for index, val := range myMap {
fmt.Printf("map[%d]=%d\n", index, val)
}
}
2.2、不同goroutine之间如何通讯
1)全局互斥锁
2)使用管道channel来解决
package main
import (
"fmt"
"sync"
"time"
)
/**
* @Description:channel管道 用于多协称之间的通信
* @Author: guai
* @Date:2020/2/25 23:49
**/
func main() {
//创建一个map保存阶乘的结果
var myMap = make(map[int]int, 10)
//声明一个全局互斥锁
//sync是包 Mutex(互斥)结构体
var lock sync.Mutex
cal := func(n int) {
res := 1
for i := 1; i < n; i++ {
res *= i
}
//为变量myMap加锁,是同一时间只能有一个协程访问
lock.Lock()
//将结果放到map中
myMap[n] = res
//解锁
lock.Unlock()
}
//开启多协程 完成任务
for i := 0; i < 130; i++ {
go cal(i)
}
//防止主线程在协程运行完之前结束
time.Sleep(time.Second * 2)
//输出结果
for index, val := range myMap {
fmt.Printf("map[%d]=%d\n", index, val)
}
}
2.3、使用channel解决上述问题(将在channel应用实例三中解决上述问题)
2.3.1、为什么使用channel
2.3.2、channel基本介绍
2.3.3、定义/声明channel
channel的使用:
package main
import "fmt"
/**
* @Description:channel 管道的使用
* @Author: guai
* @Date:2020/2/26 16:16
**/
func main() {
//1、创建一个可以存放3个int类型的管道
var intChan chan int
intChan = make(chan int, 3)
fmt.Printf("intChan的值=%v intChan本身的地址=%p\n", intChan, &intChan)
// 为管道写入数据,注意写入数据的个数不能超过其容量否则报错
intChan <- 10
num := 122
intChan <- num
intChan <- 50
fmt.Printf("channel len%v cap=%v\n", len(intChan), cap(intChan))
//从管道中取出数据 注意是取出 ,取出后管道中将不再有该值
var num2 int
num2 = <-intChan
fmt.Println("num2:", num2)
//通过观察结果可知 len为2
fmt.Printf("channel len=%v cap=%v\n", len(intChan), cap(intChan))
//继续取出管道中的值 注意当管道中的值被全部取出后继续取就会报错 deadlock
num3 := <-intChan
num4 := <-intChan
//num5:=<-intChan
fmt.Println("num3=", num3, "num4=", num4)
fmt.Printf("channel len=%v cap=%v", len(intChan), cap(intChan))
}
channel使用的注意事项
2.3.4、channel练习:
import "fmt"
/**
* @Description:使用管道保存任意数据类型
* @Author: guai
* @Date:2020/2/26 20:55
**/
type cate struct {
name string
age int
}
func main() {
//定义一个可以保存任意数据类型的管道
allChan := make(chan interface{}, 10)
//向管道中保存一个cate结构体
allChan <- cate{"guai", 12}
map1 := make(map[int]int)
map1[1] = 1
map1[2] = 2
//相管道中保存一个map
allChan <- map1
//当从次此类管道中取出数据时 需使用类型断言
//当类型与断言类型匹配时ok为true否则反之
cate1, ok := (<-allChan).(cate)
fmt.Println("name", cate1.name, "ok", ok)
map2, ok := (<-allChan).(map[int]int)
fmt.Println("map2", map2, "ok:", ok)
//也可直接遍历 但在遍历前需要先关闭channel否则会出现 deadlock的错误
//close(allChan)
//for v := range allChan {
// fmt.Println("v:", v)
//}
}
package main
import "fmt"
/**
* @Description: 完成goroutine和channel协同工作的案例
* @Author: guai
* @Date:2020/2/27 10:02
**/
func writeData(intChan chan int) {
for i := 1; i <= 500000; i++ {
//向管道中放入数据
intChan <- i
fmt.Println("wirteData:", i)
}
//关闭管道
close(intChan)
}
func readData(intChan chan int, exitChan chan bool) {
for {
//从管道中取出数据 当数据全部不取出后 ok为false v为初始默认值
v, ok := <-intChan
if !ok {
break
}
fmt.Println("readData:", v)
}
exitChan <- true
close(exitChan)
}
func main() {
//创建两个管到
intChan := make(chan int, 500000)
exitChan := make(chan bool, 1)
go writeData(intChan)
go readData(intChan, exitChan)
//主线程与协程同时运行 当协程运行结束 exitChan 为false 主线程结束
for {
_, ok := <-exitChan
if !ok {
break
}
}
}
package main
import (
"fmt"
"time"
)
/**
* @Description:channel 练习 统计1-8000的数字中,哪些是素数
* @Author: guai
* @Date:2020/2/27 10:20
**/
//将1-8000放入管道
func putNum(intChan chan int) {
for i := 1; i <= 8000; i++ {
intChan <- i
}
//关闭管道
close(intChan)
}
//从intChan中取出素数,放入primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
var flag bool
for {
//time.Sleep(time.Millisecond * 5)
//从管道中取出数据
num, ok := <-intChan
//当取不到数据时退出
if !ok {
break
}
flag = true
//判断num是不是素数
for i := 2; i < num; i++ {
if num%i == 0 {
flag = false
break
}
}
//若num是素数将num放入primeChan中
if flag {
primeChan <- num
}
}
fmt.Println("有一个primeNum协程因为取不到数据,退出")
exitChan <- true
}
func main() {
intChan := make(chan int, 1000)
primeChan := make(chan int, 20)
exitChan := make(chan bool, 4)
//开启一个协程向intChan放入 1-8000个数
go putNum(intChan)
//开启四个携程,从intChan取出数据,并判断是否为素数
for i := 0; i < 4; i++ {
go primeNum(intChan, primeChan, exitChan)
}
//如果只放不取会导致 deadLock ,故需要取出 同时
go func() {
for i := 0; i < 4; i++ {
fmt.Println(<-exitChan,"1212")
}
fmt.Println("close primeChan")
close(primeChan)
}()
//遍历primeChan,取出结果
fmt.Println("begin read")
time.Sleep(time.Second*2)
for res := range primeChan {
//输出结果
fmt.Println("素数=", res)
}
fmt.Println("主线程退出")
}
结果:
通过结果我们发现:
1)在所有协程结束前就已经开始输出结果:
原因:
我们在通过primeNum函数从intChan中取出数据并判断是否为素数并将素数存入primeChan时,主线程中的输出primeNum中的语句也在执行,也就是说并不是在判断完所有intChan中的数并将素数存入primeNum之后才开始从primeNum中读取并打印素数的,两者是并发/并行执行的
2)intChan的容量只有1000却保存了8000个整数
在putNum函数中将1-8000写入intChan中是 其实并不是一次性全部写入的,在写入过程中primeNum函数同时也在将intChan中的整数取出并判断是否为素数然后放入primeNum中两者是并发/并行执行的
2.6、channel应用实例三
package main
import "fmt"
/**
* @Description: 求1-100中每个数的阶乘
* @Author: guai
* @Date:2020/2/27 15:19
**/
/*思路:
1、将1-100个数保存到一个intChan管道中 (防止多协程取数据是发生死锁)
2、启用四个协程取从intChan取出数字并求阶乘
3、将结果保存到resChan中
4、遍历resChan输出结果
*/
func getIntChans(intChan chan int) {
//向管道中添加数据
for i := 1; i <= 100; i++ {
intChan <- i
}
//关闭管道
close(intChan)
}
func calRes(intChan chan int, resChan chan int, exitChan chan bool) {
for {
num, ok := <-intChan
//当没有从intChan中取出数据是 结束循环
if !ok {
break
}
//计算阶乘
res := 1
for i := 1; i <= num; i++ {
res *= i
}
//将结果保存到管道中
resChan <- res
}
fmt.Println("有一个协程calRes因无法取出数据而退出")
exitChan <- true
}
func main() {
intChan := make(chan int, 1050)
resChan := make(chan int, 1050)
//用于控制主线程在所有协程执行完才结束
exitChan := make(chan bool, 4)
//通过一个协程向intChan写入数据
go getIntChans(intChan)
//开启4个协程
for i := 0; i < 4; i++ {
go calRes(intChan, resChan, exitChan)
}
//只有当此协程结束 主线程才能结束 此时上面的协程也都执行完完毕
go func() {
for i := 0; i < 4; i++ {
<-exitChan
}
close(resChan)
}()
for val := range resChan {
fmt.Println(val)
}
fmt.Println("主线程结束")
}
结果:可以看出结果中存在负值和零值,是因为结果超出了int类型的最大取值范围
2.7、只读/只写管道的介绍于应用:
package main
import "fmt"
/**
* @Description:管道使用细节
* @Author: guai
* @Date:2020/2/27 16:06
**/
func main() {
//1、channel可以声明为只读,或者只写性质
//默认情况下管道是双向的 即可读可写
//1.1、声明为只写
var intChan1 chan<- int
intChan1 = make(chan int, 3)
intChan1 <- 20
fmt.Println("only write intChna1", intChan1)
//1.2、声明为只读
//声明一个可读可写的管道
var intChan3 chan int
intChan3 = make(chan int, 1)
intChan3 <- 2
var intChan2 <-chan int
//通过一个可读可写的通道初始化
intChan2 = intChan3
//intChan2<-3 //err
fmt.Println("only read intChan2:", <-intChan2)
//1.3、channel只读和只写的最佳案例实践
var ch chan int
ch = make(chan int, 10)
//用于控制主线程晚于协程结束
exitChan := make(chan struct{}, 2)
go send(ch, exitChan)
go recv(ch, exitChan)
var total = 0
for _ = range exitChan {
total++
if total == 10 {
break
}
}
fmt.Println("over...")
}
//用于写管道的函数
func send(ch chan<- int, exitChan chan struct{}) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
var a struct{}
exitChan <- a
}
//用于读管道的函数
func recv(ch <-chan int, exitChan chan struct{}) {
for {
v, ok := <-ch
if !ok {
break
}
fmt.Println(v)
var a struct{}
exitChan <- a
}
}
2.8、select的介绍即用于解决管道取数据的阻塞问题
package main
import (
"fmt"
)
/**
* @Description:使用select解决从管道取数据的阻塞问题
* @Author: guai
* @Date:2020/2/27 16:37
**/
func main() {
//在实际开发中我们有时无法确定什么时候关闭管道
//此时可使用 当一个case后的读取阻塞将会自动到下一个case匹配知道default
//select {
// case: 读取管道
// case : 读取管道
// default: 当没有主动关闭管道是将会执行此 case
//}
//定义一个存放10个数据的管道
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
intChan <- i
}
//定义一个存放5个数据的管道
strChan := make(chan string, 5)
for i := 0; i < 5; i++ {
strChan <- "hello" + fmt.Sprint("%d", i)
}
//遍历管道
for {
select {
//注意:如果intCHan一致读取不到不会一致阻塞而是自动到下一个case匹配
case v := <-intChan:
fmt.Println("intChan:", v)
//time.Sleep(time.Second)
case v := <-strChan:
fmt.Println("strChan:", v)
//time.Sleep(time.Second)
default:
fmt.Println("读取不到,over...")
//time.Sleep(time.Second)
return
}
}
}
结果:
2.9、goroutine中使用recover,解决协程中出现的panic
例;
package main
import (
"fmt"
"time"
)
/**
* @Description:协程中的恐慌处理
* @Author: guai
* @Date:2020/2/27 17:30
**/
func sayHello() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Println("hello World")
}
}
func test() {
//使用defer+recover
defer func() {
//捕获test抛出的panic
if err := recover(); err != nil {
fmt.Println("test() 发生错误", err)
}
}()
var myMap map[int]string
myMap[0] = "golang" //error
}
func main() {
go sayHello()
go test()
for i := 0; i < 10; i++ {
fmt.Println("main() ok=", i)
time.Sleep(time.Second)
}
}