• [go学习笔记.第十四章.协程和管道] 1.协程的引入,调度模型以及运行cpu数目,协程资源竞争问题


    一.协程的引入

    1.先看一个需求

    需求:

            要求统计 1~9000000000 的数字中,哪些是素数?

    分析思路:

            (1).传统的方法,就是使用一个循环,循环的判断各个数是不是素数.(很慢)

             (2).使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成,这时就会使用到goroutine(速度提高4倍)

    2.进程和线程介绍

    (1).进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位

    (2).线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位

    (3).一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行

    (4).一个程序至少有一个进程,一个进程至少有一个线程

    3.程序,进程和线程的关系示意图

     4.并行和并发

    (1).多线程程序在单核上运行,就是并发

    (2).多线程程序在多核上运行,就是并行

    示意图说明:

     小结:

    并发

            因为是在一个 cpu上,比如有 10 个线程,每个线程执行10毫秒(进行轮询操作),从人的角度看,好像这10个线程都在运行,但是从微观的角度看,在某一个时间点看,其实只有一个线程在执行,这就是并发

    并行:

           因为是在多个 cpu上(比如有10个cpu),比如有 10 个线程,每个线程执行10毫秒(各自在不同cpu上执行),从人的角度看,这10个线程都在运行,但是从微观的角度看,在某一个时间点看,也同时有10个线程在执行,这就是并行

    5.go协程和主线程

    go主线程(有程序员直接称为线程,也可以理解成进程),一个go线程上,可以起多个协程,你可以这样理解,协程是轻量级的线程[编译器做优化]

    go协程的特点

    (1).有独立的栈空间

    (2).共享程序堆空间

    (3).调度由用户控制

    (4).协程是轻量级的线程

    go主线程和协程关系示意图:

    6.协程的快速入门

    案例说明:

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

    (1).在主线程(可以理解成进程)中,开启一个 goroutine :该协程每隔 1 秒输出”hello world"

    (2 ).在主线程中也每隔一秒输出"hello golang" ,输出 10 次后,退出程序

    (3).要求主线程和 goroutine 同时执行

    (4).画出主线程和协程执行流程图

    1. package main
    2. import (
    3. "fmt"
    4. "time"
    5. "strconv"
    6. )
    7. //在主线程(可以理解为进程)中,开启一个goroutine,该协程每隔1秒输出"hello world
    8. //在主线程中也每隔1秒输出"hello go",输出10次后,退出程序
    9. //要求主线程和goroutine同时执行
    10. //每隔1秒输出"hello world"
    11. func test() {
    12. for i := 0; i <= 10; i++ {
    13. fmt.Println("hello world " + strconv.Itoa(i))
    14. time.Sleep(time.Second)
    15. }
    16. }
    17. func main() {
    18. go test() //开启一个协程
    19. for i := 0; i <= 10; i++ {
    20. fmt.Println("main hello go " + strconv.Itoa(i))
    21. time.Sleep(time.Second)
    22. }
    23. }

    输出效果说明:main这个主线程和test协程同时执行 

    协程示意图

    7.小结

    (1).主线程是一个物理线程,直接作用在cpu上的,是重量级的,非常耗费cpu资源

    (2).协程从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对小

    (3).Golang 的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显 Golang 在并发上的优势了

    二.goroutine的调度模型,设置运行cpu数目,协程资源竞争问题以解决方法

    1.MPG模式基本介绍

    2.MPG模式运行的状态

    I.运行状态

     II.运行状态

     3.设置Golang运行的cpu数量

    (1).go1.8以后,默认让程序运行在多个核上,可以不用设置

    (2).go1.8前,还需要设置一下,可以更高效的利用cpu

    设置cpu数量代码如下:

    1. package main
    2. import (
    3. "fmt"
    4. "runtime"
    5. )
    6. func main() {
    7. cpuNum := runtime.NumCPU() // 获取本地机器逻辑CPU个数
    8. fmt.Printf("cpu num = %v\n", cpuNum)
    9. //设置可使用的cpu个数
    10. runtime.GOMAXPROCS(cpuNum - 1)
    11. fmt.Println("ok")
    12. }

    4.channel(管道)的引入

    需求:
            现在要计算 1 ~ 200 的各个数的阶乘,并且把各个数的阶乘放入到map中,最后显示出来,要求使用 goroutine完成
    分析思路:
            (1).使用goroutine来完成,效率高,但是会出现并发/并行安全问题
            (2).这里就提出了不同goroutine如何通信的问题
    代码实现:
           (1).使用 goroutine 来完成(看看使用 gorotine 并发完成会出现什么问题?然后会去解决这个问题) 
           (2).在运行某个程序时,如何知道是否存在资源竞争问题。方法很简单,在编译该程序时,增加一个参数 -race 即可
    

    示意图: 

    代码如下: 

    1. package main
    2. import (
    3. "fmt"
    4. "time"
    5. )
    6. //需求: 计算1~200的各个数的阶乘,并把各个数的阶乘放到map中,最后显示出来,要求使用goroutine完成
    7. //思路:
    8. //1.编写一个函数,计算各个数的阶乘,并放到map中
    9. //2.启动多个协程,统计的结果放入map中
    10. //3.map应该是一个全局的
    11. var (
    12. myMap = make(map[int]int 10)
    13. )
    14. //计算n的阶乘n!,并把放到map中
    15. func test(n int) {
    16. res := 1
    17. for i := 0; i<= n; i++ {
    18. res *= i
    19. }
    20. //放入map中
    21. myMap[n] = res
    22. }
    23. func main() {
    24. //开启多个协程完成任务
    25. for i := 0; i <= 200; i++ {
    26. go test()
    27. }
    28. // 休眠10秒
    29. time.Sleep(time.Second * 10)
    30. //输出结果
    31. for i, v := range myMap {
    32. fmt.Printf("map[%v] = %v \n", i, v)
    33. }
    34. }

    5.不同goroutine之间如何通讯

    (1).全局变量的互斤锁

    (2).使用管道channel来解决

    6. 使用全局变量加锁同步改进程序

    • 因为没有对全局变量 m 加锁,因此会出现资源争夺问题,代码会出现错误,提示concurrent map writes
    • 解决方案:加入互斥锁
    • 我们的数的阶乘很大,结果会越界,可以将求阶乘改成 sum += uint64(i)

    代码改进:

    1. package main
    2. import (
    3. "fmt"
    4. "time"
    5. "sync"
    6. )
    7. //需求: 计算1~200的各个数的阶乘,并把各个数的阶乘放到map中,最后显示出来,要求使用goroutine完成
    8. //思路:
    9. //1.编写一个函数,计算各个数的阶乘,并放到map中
    10. //2.启动多个协程,统计的结果放入map中
    11. //3.map应该是一个全局的
    12. var (
    13. myMap = make(map[int]int, 10)
    14. //声明一个全局的互斥锁
    15. //lock:是一个全局的互斥锁
    16. //sync是包: synchornized(同步)
    17. //Mutex:是互斥
    18. lock sync.Mutex
    19. )
    20. //计算n的阶乘n!,并把放到map中
    21. func test(n int) {
    22. res := 1
    23. for i := 1; i<= n; i++ {
    24. res *= i
    25. }
    26. //放入map中
    27. //加锁
    28. lock.Lock()
    29. myMap[n] = res
    30. //解锁
    31. lock.Unlock()
    32. }
    33. func main() {
    34. //开启多个协程完成任务
    35. for i := 1; i <= 20; i++ {
    36. go test(i)
    37. }
    38. // 休眠10秒
    39. time.Sleep(time.Second * 5)
    40. //输出结果
    41. lock.Lock()
    42. for i, v := range myMap {
    43. fmt.Printf("map[%v] = %v \n", i, v)
    44. }
    45. lock.Unlock()
    46. }

    [上一节][go学习笔记.第十三章.单元测试] 1.单元测试

    [下一节]学习笔记.第十四章.协程和管道] 2.管道 

  • 相关阅读:
    强连通,奇怪的缩点学习笔记?
    阿里巴巴中国站1688按关键词搜索商品api电商数据接口
    Win10使用nginx,注册到服务设置自启与后台运行,解决 Access is denied 问题
    计算机毕业设计(附源码)python舟影短视频平台
    1002 A+B for Polynomials
    Spring Security实现统一登录与权限控制
    PyTorch中torch.gather()函数
    关于C2447 “{”: 缺少函数标题(是否是老式的形式表?)
    华为机试真题 C++ 实现【火星文计算】
    常见html+css面试题
  • 原文地址:https://blog.csdn.net/zhoupenghui168/article/details/127759589