1 goroutine 协程
//1 并发和并行
并发:同一时间段内,多个任务在执行(单个cpu,执行多个任务)
并行:同一时刻,多个任务在执行(多个cpu的支持)
//注:
编程语言中,因为Python有GIL全局解释器锁,导致同一时刻,同一个进程中只能运行一个线程
===> 延伸出开启多进程,解决利用上多核优势
其他语言并发,不存在开启多进程一说,直接开启多线程,就会利用上多核优势
go并发,没有开启多进程、多线程一说,直接开启go协程 goroutine //实质是协程 + 线程池
//2 go协程 goruotine 的开启方法
任务函数调用前 加关键字 go
eg: go test()
//3 大小
协程(goroutine) : 2kb大小
线程 : 几兆(M)
//go协程会复用线程,就是GMP模型中,从线程池的一堆线程M中,通过Processor调度器P,去挂载执行go的协程G
//4 协程通信
goroutine之间通信,通过 信道channel 通信
go推崇用信道通信,而不推崇用共享变量通信(锁,死锁)
//5 go语言的GMP模型
-G: 开的goroutine
-M: M当成操作系统真正的线程,实际上是用户线程,再对应到内核线程(操作系统线程)
-P: Processor:goroutine的调度器,是一个队列
gomaxprocs默认是cpu核数(可以当做cpu核数)
//用户线程 与 内核线程 的映射关系
python : 开的线程是用户线程,用户线程跟内核线程 是1:1的对应关系
某些语言: 用户线程和内核线程 是n:1的关系
go : 用户线程和内核线程 是n:m的关系 //m < n,一般把m设置为CPU核数
package main
import (
"fmt"
"runtime"
"time"
)
func test() {
fmt.Println("go go go")
}
func main() {
fmt.Println("主线程开始执行")
go test() //方法调用前 加关键字 go --> 开启一个goroutine
go test()
go test()
go test()
go test()
go test()
go test()
go test()
time.Sleep(1*time.Second)
fmt.Println("主线程结束执行")
//go语言中,主线程不会等待goroutine执行完成,要等待它结束需要自己额外处理
}
func main() {
fmt.Println("主线程开始执行")
for i:=0;i<10;i++ {
go func(){
fmt.Println("go go go ")
}()
}
time.Sleep(1*time.Second)
fmt.Println("主线程结束执行")
}
//eg: GMP模型
func main() {
// 设置P的大小,认为是cpu核数即可
runtime.GOMAXPROCS(1)
fmt.Println("主线程开始执行")
go func() {
for {
fmt.Println("xxxx")
}
}()
for i := 0; i < 10; i++ {
go func() {
for {
fmt.Println("我是死循环")
}
}()
}
time.Sleep(10 * time.Second)
fmt.Println("主线程结束执行")
}
2 信道(通道)
2.1 基本信道
//不同goroutine之间通信,通过信道channel实现
信道 其实在内存中也是一个共享变量,只不过是处理了线程安全(就是自动有加锁的处理)
package main
import (
"fmt"
"time"
)
func main() {
//1 定义channel
var c chan int //定义了一个int类型的信道,协程往里存取数字
//2 信道的零值
引用类型,空值为nil
当做参数传递时,不需要取地址,改的就是原来的
需要初始化再使用
fmt.Println(c)
//3 信道初始化
c=make(chan int) //数字暂时先不关注
//4 信道放值 (注意赋值和放值)
c <- 1
c = 12 //赋值直接报错
//5 信道取值
<- c
//6 取出来赋值给一个变量 int
var a int
a = <- c
a := <- c //推导类型: 定义变量a
//7 信道默认情况下,不管放值还是取值,都是阻塞的
必须是至少有两个协程 同时对接存取值时,放取值的协程 才不阻塞,继续向下执行
若只是有单个放值 或 取值的协程,会报错死锁: deadlock
go test1(c) //开启了一个协程,执行test1函数,向信道c放值 10
b := <- c //阻塞 不但实现了两条协程之间通信,还实现了等待协程执行结束
fmt.Println(b)
}
func test1(a chan int) {
time.Sleep(1*time.Second)
fmt.Println("go go go ")
//往信道中放一个值
a <- 10 //阻塞
}
2.2 信道小案例
//信道小例子
程序有一个数中 每一位的平方和与立方和,然后把平方和与立方和相加并打印出来
package main
import (
"fmt"
"time"
)
func calcSquares(number int, squareop chan int) {
sum := 0 //总和
for number != 0 {
digit := number % 10 //589对10取余数,9 8 5
sum += digit * digit //sum=9*9 8*8 5*5
number /= 10 //num=58 5 0
}
time.Sleep(2 * time.Second)
squareop <- sum
}
func calcCubes(number int, cubeop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit * digit
number /= 10
}
time.Sleep(1 * time.Second)
cubeop <- sum
}
func main() {
ctime := time.Now().Unix()
fmt.Println(ctime)
number := 589
sqrch := make(chan int)
cubech := make(chan int)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
//squares:= <-sqrch
//cubes:=<-cubech
fmt.Println("Final output", squares+cubes)
ttime := time.Now().Unix()
fmt.Println(ttime)
fmt.Println(ttime - ctime)
}
2.3 信道死锁、单向信道、关闭信道
//1 信道的死锁现象 deadlock
默认在多个goroutine中,放值、取值都是阻塞的
在一个goroutine中,出现若只是有单个放值 或 取值的,就会出现死锁
package main
import "fmt"
//信道死锁eg:
func main() {
var c chan int = make(chan int)
c<-1 //其实放不进去,阻塞在这,就死锁了
<-c //没有,取不到,阻塞在这,就死锁了
}
//2 单向信道:只写 或 只读 chan<- 了解
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
//sendch := make(chan<- int) //定义了一个只写信道
sendch := make(chan int) //定义了一个可读可写信道
go sendData(sendch) //传到函数中转成只写信道,在goroutine中,只负责写,不能往外读,主协程读
fmt.Println(<-sendch) //只写信道一旦读,就有问题
}
///3 关闭信道
func main() {
sendch := make(chan int)
//关闭信道
close(sendch)
//信道可以用for循环遍历
}
//for循环 遍历信道,如果不关闭会报死锁,如果关闭了,放不进去,循环结束
func producer(chnl chan int) {
for i := 0; i < 100; i++ {
fmt.Println("放入了",i)
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for v := range ch { // for range 遍历循环
fmt.Println("Received ",v)
}
}
2.4 缓冲信道、信道其他
//1 缓冲信道 就是可以指定 信道的长度
只在缓冲已满的情况,才会阻塞向缓冲信道,只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var c chan int =make(chan int,6) //无缓冲信道数字是0
c<-1
c<-2
c<-3
c<-5
c<-5
c<-6
c<-7 //死锁
<-c
<-c
<-c
<-c
<-c
<-c
<-c //取空了,死锁
//2 长度 vs 容量
fmt.Println(len(c)) //目前放了多少
fmt.Println(cap(c)) //可以最多放多少
}
//3 WaitGroup 等待所有goroutine执行完成
func process1(i int, wg *sync.WaitGroup) {
fmt.Println("started Goroutine ", i)
time.Sleep(2 * time.Second)
fmt.Printf("Goroutine %d ended\n", i)
//一旦有一个完成,减一
wg.Done()
}
func main() {
var wg sync.WaitGroup //没有初始化,值类型,当做参数传递,修改原值,需要取地址(指针)
//fmt.Println(wg)
for i:=0;i<10;i++ {
wg.Add(1) //启动一个goroutine,add加1
go process1(i,&wg)
}
wg.Wait() //一直阻塞在这,直到调用了10个done,计数器减到零
}
//4 使用信道如何实现 等待所有goroutine(协程)执行完成?
2.5 Select(信道选择)
//select语句: 用于在多个 发送/接受 信道操作中进行选择
select语句会一直阻塞,直到发送/接收操作准备就绪,谁先准备就绪,就先执行谁的case
如果有多个信道操作 同时准备完毕,select会随机地选取其中之一执行
该语法与 switch类似,所不同的是,这里的每个 case语句都是信道操作
//应用场景:***
1.异步发送多个请求,谁先回来,就先处理谁
2.非阻塞式IO,请求没回来,处理其他事情
package main
import (
"fmt"
"time"
)
func server1(ch chan string) {
time.Sleep(6 * time.Second)
ch <- "from server1"
}
func server2(ch chan string) {
time.Sleep(3 * time.Second)
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
//开启两个协程执行server
go server1(output1)
go server2(output2)
select {
case s1 := <- output1:
fmt.Println(s1,"ddddd")
case s2 := <- output2:
fmt.Println(s2,"yyyy")
}
}
===================================================
func process(ch chan string) {
time.Sleep(10500 * time.Millisecond)
ch <- "process successful"
}
//eg: for + select 模拟实现了非阻塞式io,若信道数据没有获取,执行其他的事
func main() {
ch := make(chan string)
go process(ch)
for {
time.Sleep(1000 * time.Millisecond)
select {
case v := <-ch:
fmt.Println("received value: ", v)
return
default:
// 可以干其他事,模拟非阻塞式io
fmt.Println("no value received")
}
}
}
//死锁
func main() {
ch := make(chan string)
select {
case <-ch:
}
}
//信道操作都准备完毕,会随机选取case执行
func server1(ch chan string) {
ch <- "from server1"
}
func server2(ch chan string) {
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
time.Sleep(1 * time.Second)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
3 mutex 互斥锁
//1 使用锁的场景
多个goroutine通过 共享内存(共享变量) 实现数据通信
//2 临界区
当程序并发地运行时,多个[Go 协程]同时修改共享资源的代码。这些修改共享资源的代码称为临界区。
//如果在任意时刻只允许一个 Go 协程访问临界区,那么就可以避免竞态条件。而使用 Mutex 可以达到这个目的
//3 总结
1.不同goroutine之间传递数据:共享变量、通过信道
2.如果是修改共享变量,建议加锁
3.如果是协程之间通信,用信道
package main
import (
"fmt"
"sync"
)
==================================================================
//含有竞态条件的程序: 1000个协程,每个协程都是 x += 1 的操作
var x = 0
func increment(wg *sync.WaitGroup) {
x = x + 1
wg.Done()
}
func main() {
var w sync.WaitGroup
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w)
}
w.Wait()
fmt.Println("final value of x", x)
}
//eg: 通过共享变量 + 互斥锁
var x = 0 //全局变量,各个goroutine都可以拿到并且操作
func increment(wg *sync.WaitGroup,m *sync.Mutex) {
m.Lock()
x = x + 1
m.Unlock()
wg.Done()
}
func main() {
var w sync.WaitGroup
var m sync.Mutex //定义一个全局锁,锁是个值类型,函数传递需要传地址
fmt.Println(m)
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w,&m)
}
w.Wait()
fmt.Println("final value of x", x)
}
==================================================================
//eg: 通过信道来做
var x = 0
func increment(wg *sync.WaitGroup, ch chan bool) {
ch <- true //缓冲信道放满了,就会阻塞 等到信道数据被取出后,才会执行放值
x = x + 1
<- ch
wg.Done()
}
func main() {
var w sync.WaitGroup
ch := make(chan bool, 1) //定义了一个有缓存,容量大小为1的信道
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w, ch)
}
w.Wait()
fmt.Println("final value of x", x)
}
4 异常处理
//1 异常处理
defer : 延迟执行,并且即便程序出现严重错误,也会执行 defer v.延迟
panic : 主动抛出异常 //相当于python的 raise panic n.恐慌
recover : 恢复程序,继续执行 recover v.恢复
defer 主要是 含有defer语句的函数,会在当前这个函数将要返回之前,再调用另一个函数(defer 语句后面的函数)
//2 捕获异常--通用方式:
//python
try:
可能会错误的代码
except Exception as e:
出错了怎么处理的代码
print(e)
finally:
无论是否出错,都会执行
//go
// 1.先写 defer注册
defer func() { //该匿名函数永远会执行
if error:= recover(); error!=nil{
//except的东西
fmt.Println(error)
}
//相当于finally,无论是否出错,都会执行
fmt.Println("我永远会执行,不管是否出错")
}()
//2.再写 可能会错误的代码
panic("可能会出错的代码")
package main
import (
"fmt"
"os"
)
func main() {
defer fmt.Println("我最后执行") //注册一下,并不执行,等main函数执行完了以后,从下往上执行defer定义的东西
defer fmt.Println("我倒数第二个打印")
fmt.Println("我先执行")
//模拟切片出错了
var a []int
fmt.Println(a[10])
//主动抛出异常
panic("我出错了")
fmt.Println("ccccc") //这句就不会执行了,但defer注册的语句 最后都会执行
==========================================================
//假设打开一个文件,中间可能要出错,就可以把关闭文件 用defer延迟执行
f:=open()
defer f.close()
//出错了
}
====================================================
func f1(){
fmt.Println("f1 f1")
}
func f2(){
defer func() { //这个匿名函数永远会执行
//error:=recover() //恢复程序继续执行
//fmt.Println(error) //如果没有错误,执行recover会返回nil
如果有错误,执行recover会放错误信息
if error := recover(); error != nil {
//表示出错了,打印一下错误信息,程序也恢复了,会继续往下执行
fmt.Println(error)
}
//相当于finally
fmt.Println("我永远会执行,不管是否出错")
}()
fmt.Println("f2 f2")
//panic("主动抛出错误")
}
func f3(){
fmt.Println("f3 f3")
}
func main() {
//捕获异常,处理异常,让程序继续运行
f1()
f2()
f3()
}
//3 go的错误处理 go语言每个语句都有可能出错,故都可以返回接受 错误的信息
func main() {
f, err := os.Open("/test.txt")
// 有错误必须处理完,才能向下执行
if err != nil {
fmt.Println(err)
return
}
fmt.Println(f.Name(), "opened successfully")
}
5 Gin框架beego的使用
# 1 go语言中的web框架
gin beego Iris Echo
# 2 python的web框架
django flask,tornado,sanic,fastapi
# 3 gin:
小巧精简的框架,类似于flask,使用第三方插件
# 4 beego:
大而全,类似于django,缓存,session...
# 5 beego快速入门:中国人写的,文档全是中文的
-go env -w GOPROXY=https://goproxy.io,direct 设置代理
-go get github.com/astaxie/beego
-go get github.com/beego/bee 等同于djangoadmin创建项目,创建beego项目
-go list 安装项目依赖的模块