zoukankan      html  css  js  c++  java
  • Go并发编程(goroutine)

    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 := <-job
    		sum := int64(0)
    		n := data.x
    
    		for n > 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"
    	"github.com/julienschmidt/httprouter"
    	"io"
    	"log"
    	"net/http"
    )
    
    //var once sync.Once
    
    func helloHandler(w http.ResponseWriter, req *http.Request) {
    	io.WriteString(w, "hello, world!
    ")
    
    	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)
    }
    
    
    
    

    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  // 即可在浏览器中看到可视化的性能图解
    
    
  • 相关阅读:
    前端vue使用js-xlsx导出excel的三种方法
    vue使用echarts绘制河南地图并实现个人轨迹
    VUE中使用Echarts绘制柱状图
    springboot项目配置拦截器
    springboot+mybatis的多数据源配置
    Java后端高频面试题汇总
    Java基础专题
    Java集合专题
    Java并发专题
    JVM专题javascript:;
  • 原文地址:https://www.cnblogs.com/zjaiccn/p/14667307.html
Copyright © 2011-2022 走看看