Go并发
并发编程里面一个非常重要的概念, go语言在语言层面天生支持并发, 这也是Go语言流行的一个重要的原因
Go语言中的并发编程
并发与并行
并发:同一时间段内执行多个任务(你在用微信和两个人聊天)
并行:同一时刻执行多个任务 (你和你的朋友 都在用微信和 你们的一个朋友聊天)
Go语言的并发通过goroutine 实现 , goroutine 是比线程更加轻量级的协程 。goroutine是由Go语言的运行时(runtime)调度完成,而线程是由操作系统调度完成
Go语言还提供channel在多个goroutine间进行通信,goroutine和channel是Go语言秉承的CSP并发模式的重要实现基础
package main import ( "fmt" "sync" ) //func Person() { // fmt.Println(12356) //} func main() { var wg sync.WaitGroup defer wg.Done() for i:=0; i<10000; i++ { //go Person() // 开启一个单独的goroutine取执行hello函数(任务) go func(i int) { wg.Add(1) fmt.Println(12355, i) }(i) fmt.Println("main") // 如果main打印出来 说明整个线程都死了 go就执行不了Person了 } wg.Wait() }
- goroutine 通过sync.waitgroup节省负载
package main import ( "fmt" "math/rand" "sync" ) var wg sync.WaitGroup func main() { for i := 0; i < 1000; i++ { go func(i int) { wg.Add(1) fmt.Println(rand.Intn(1000)) // rand.Intn(1000) 1000 以内的随机数 defer wg.Done() }(i) fmt.Println("main") } wg.Wait() }
- goroutine调度
GMP是Go语言运行时(runtime)层面实现的, 是go语言自己实现的一套调度系统. 区别于操作系统调度OS线程。
G:就是goroutine里除了存放goroutine信息外还有所在P的绑定信息
M:machine 是Go运行时对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系,一个goroutine最终是要放到M上运行的
P:管理者一组goroutine队列,P里面会存储当前goroutine运行的上下文环境, 自己队列执行完成,就回去全局队列取任务,全局完成,就去别的P队列抢任务 干活。活活的活雷锋
P的个数是通过runtime.GOMAXPROCS设定最大256 。go1.5版本之后默认为物理线程数, 在并发量大的时候会增加一些p和m但是不会太多,不会太多,切换太频繁会得不偿失
Go语言中的操作系统线程和goroutine的关系
- 一个操作系统线程对应用户态多个goroutine
- go程序可以同时使用多个操作系统线程
- goroutine和OS线程是多对多的关系 m:n。将m个goroutine分配给n个os的线程去执行
Channel通道
单纯的将函数并发执行意义没有多大的,函数与函数之间是需要传参交换数据才能体现出并发函数的意义
Go语言的并发模型提倡通过通信共享内存 而不是通过共享内存而实现通信
Go语言中的通道是一种特殊的类型。通道像一个传送带或着队列,总是遵循先进先出的规则,保证收发数据的顺序
package main import "fmt" func main() { var ch chan interface{} 声明通道 ch = make(chan interface{}) // 通道初始化 ch := make(chan interface{}, 16) // 带缓冲区的通道初始化 ch <- 10 // <- 发送值 和接收值 都是这个符号 res := <-ch // 接收值 close(ch) //关闭通道 fmt.Println(ch) }
- Channel 练习
var wg sync.WaitGroup func c1(ch1 chan interface{}) { defer wg.Done() for i := 0; i < 100; i++ { ch1 <- rand.Intn(100) } close(ch1) // 必须关闭 否则 会出现死锁 } func c2(ch1, ch2 chan interface{}) { defer wg.Done() for value := range ch1{ ch2 <- value.(int) * value.(int) } //for { // i,ok := <-ch1 // if !ok { // break // } // ch2 <- i.(int) * i.(int) //} close(ch2) // 必须关闭否则会出现死锁 //fmt.Println(ch2) } func main() { v1 := make(chan interface{}, 100) v2 := make(chan interface{}, 100) wg.Add(2) go c1(v1) go c2(v1, v2) fmt.Println(v2) for i := range v2{ fmt.Println(i) } wg.Wait() }
- 单向通道
ch1 chan <- int 只能存 ch1 <- chan int 只能取
- worker pool(goroutine池)
编写代码实现一个计算随机数的被一个位置数字子和的程序 ,使用goroutine和channel 构建模型
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func worker(id int, jobs <-chan int, list chan<- int) { defer wg.Done() for item := range jobs { fmt.Println(id, "start", item) time.Sleep(1 * time.Second) fmt.Println(id, "ending", item) list <- item * 2 } } func main() { jobs := make(chan int, 100) list := make(chan int, 100) for i := 0; i < 3; i++ { go worker(i, jobs, list) wg.Add(1) } for i := 0; i < 5; i++ { jobs <- i } close(jobs) wg.Wait() //for value := range list { // fmt.Println(value) //} //time.Sleep(3 * time.Second) } 执行结果: 2 start 0 1 start 2 0 start 1 0 ending 1 0 start 3 2 ending 0 1 ending 2 2 start 4 2 ending 4 0 ending 3
- 复杂一点的channel_goroutine
package main import ( "fmt" "math/rand" "sync" "time" ) //import ( // "fmt" // "sync" // "time" //) // //var wg sync.WaitGroup // //func worker(id int, jobs <-chan int, list chan<- int) { // defer wg.Done() // wg.Add(1) // for item := range jobs { // // fmt.Println(id, "start", item) // time.Sleep(1 * time.Second) // fmt.Println(id, "ending", item) // // list <- item * 2 // // } //} // //func main() { // jobs := make(chan int, 1000) // list := make(chan int, 1000) // // for i := 0; i < 64; i++ { // go worker(i, jobs, list) // // } // // for i := 0; i < 1000; i++ { // jobs <- i // } // close(jobs) // //close(list) // //for value := range list { // // fmt.Println(value) // //} // wg.Wait() // // //} /* 1.开启一个goroutine循环生成int64的所计数 发送到jobChan 2.开启24个goroutine从jobChan中取出随机数并计算各位数的和 将结果流入res 3.主goroutine从res取出结果并打印 */ type Job struct { x int64 } type Res struct { job *Job result int64 } var wg sync.WaitGroup func worker(job chan<- *Job) { for { job <- &Job{x: rand.Int63n(1000000000)} time.Sleep(500 * time.Millisecond) } } func rework(job <-chan *Job, res chan<- *Res) { defer wg.Done() for { data := 0 { sum += n % 10 n = n / 10 } res <- &Res{job: data, result: sum} } } func main() { wg.Add(1) job := make(chan *Job, 100) res := make(chan *Res, 100) go worker(job) wg.Add(24) for i := 0; i < 24; i++ { go rework(job, res) } for item := range res{ fmt.Println(item, item.result, item.job.x) } wg.Wait() }
- Select 多路复用
某些场景下我们需要同时从多个听到接收数据。通道接收数据时,如果没有数据可以接收
Go HTTP 包
package main import ( "encoding/json" "fmt" "/julienschmidt/httprouter" "io" "log" "net/http" ) //var once sync.Once func helloHandler(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "hello, world!\n") log.Println(req.RequestURI, req.RemoteAddr,req.Host, req.Method) } func main() { // 创建路由 router := httprouter.New() // 设置路径 router.POST("/v1", posthello) router.GET("/", gethtml) // http.HandleFunc("/", helloHandler) fmt.Println("[+++++++++++++++]") _ = http.ListenAndServe(":12345", router) } func posthello(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { io.WriteString(w,"123post") } func gethtml(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { // io.WriteString(w, "v1 get") // json序列化 text,_ := json.Marshal(map[string]string{"text":"ccn"}) w.Write(text) }
- Golang中文标准库 https:///pkgdoc
GO语言单元测试
测试函数的覆盖率: > 90%
测试整体代码覆盖率: > 60%
单元测试的文件名必须以_test.go 结尾
测试的函数名必须以Test开头
func TestSplit(t *testing.T) go test -cover -coverprofile=cover.out // 生成cover文件 go tool cover -html=cover.out // 通过浏览器打开可视化界面
- 基准测试
基准测试就是在一定的工作负载之下检测程序性能的一种方法。
测试函数的函数名必须是Benchmark 开头
func BenchmarkSplit(b *testing.B) go test -bench=Split
- pprof调试工具
Go语言项目中的性能优化主要有以下几个方面
cpu profile :报告程序的cpu使用情况 按照一定的频率去采集应用程序在cpu和寄存器上的数据
memory profile : 报告内存使用情况
block profile : 用来分析和查找死锁等性能瓶颈
goroutine profile : 。。。
- cpu性能分析
// 开始start pprof.startcpuprofile(w io.writer) // 结束stop pprof.stopcpuprofile()
- gin使用pprof 性能分析
pprof.Register(router) go tool pprof http://localhost:9000/debug/pprof/goroutine?second=20 web // 即可在浏览器中看到可视化的性能图解