[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).画出主线程和协程执行流程图

package main

import (
    "fmt"
    "time"
    "strconv"
)

//在主线程(可以理解为进程)中,开启一个goroutine,该协程每隔1秒输出"hello world
//在主线程中也每隔1秒输出"hello go",输出10次后,退出程序
//要求主线程和goroutine同时执行

//每隔1秒输出"hello world"
func test() {
    for i := 0; i <= 10; i++ {
        fmt.Println("hello world " + strconv.Itoa(i))
        time.Sleep(time.Second)
    }
}

func main() {
    go test()   //开启一个协程
    
    for i := 0; i <= 10; i++ {
        fmt.Println("main hello go " + strconv.Itoa(i))
        time.Sleep(time.Second)
    }
}

输出效果说明: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数量代码如下:

package main

import (
    "fmt"
    "runtime"
)

func main()  {
    cpuNum := runtime.NumCPU() // 获取本地机器逻辑CPU个数
    fmt.Printf("cpu num = %v\n", cpuNum)
    //设置可使用的cpu个数
    runtime.GOMAXPROCS(cpuNum - 1)
    fmt.Println("ok")
}

4.channel(管道)的引入

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

示意图: 

代码如下: 

package main

import (
    "fmt"
    "time"
)


//需求: 计算1~200的各个数的阶乘,并把各个数的阶乘放到map中,最后显示出来,要求使用goroutine完成
//思路:
//1.编写一个函数,计算各个数的阶乘,并放到map中
//2.启动多个协程,统计的结果放入map中
//3.map应该是一个全局的 

var (
    myMap = make(map[int]int 10)
)
//计算n的阶乘n!,并把放到map中
func test(n int)  {
    res := 1
    for i := 0; i<= n; i++ {
        res *= i
    }
    //放入map中
    myMap[n] = res
}
func main() {
    //开启多个协程完成任务
    for i := 0; i <= 200; i++ {
        go test()
    }

    // 休眠10秒
    time.Sleep(time.Second * 10)
    //输出结果
    for i, v := range myMap {
        fmt.Printf("map[%v] = %v \n", i, v)
    }
}

5.不同goroutine之间如何通讯

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

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

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

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

代码改进:

package main

import (
    "fmt"
    "time"
    "sync"
)


//需求: 计算1~200的各个数的阶乘,并把各个数的阶乘放到map中,最后显示出来,要求使用goroutine完成
//思路:
//1.编写一个函数,计算各个数的阶乘,并放到map中
//2.启动多个协程,统计的结果放入map中
//3.map应该是一个全局的 

var (
    myMap = make(map[int]int, 10)
    //声明一个全局的互斥锁
    //lock:是一个全局的互斥锁
    //sync是包: synchornized(同步)
    //Mutex:是互斥
    lock sync.Mutex
)
//计算n的阶乘n!,并把放到map中
func test(n int)  {
    res := 1
    for i := 1; i<= n; i++ {
        res *= i
    }
    //放入map中
    //加锁
    lock.Lock()
    myMap[n] = res
    //解锁
    lock.Unlock()
}
func main() {
    //开启多个协程完成任务
    for i := 1; i <= 20; i++ {
        go test(i)
    }

    // 休眠10秒
    time.Sleep(time.Second * 5)
    //输出结果
    lock.Lock()
    for i, v := range myMap {
        fmt.Printf("map[%v] = %v \n", i, v)
    }
    lock.Unlock()
}

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

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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《Go语言学习笔记.pdf》是一本关于Go语言学习学习笔记,内容丰富且简洁明了。本书从基础知识开始,逐步介绍了Go语言的语法、特性和常用库函数等。在学习笔记中,作者通过实际的示例和练习帮助读者理解Go语言的概念和用法。 第一章介绍了Go语言的起源和发展,为读者提供了对Go语言背景的整体了解。第二章讲解了Go语言的基本语法,例如变量声明、循环和条件语句等。通过大量的代码示例,读者能够更好地理解Go语言的语法和结构。 接下来的章节重点介绍了Go语言的并发编程和高级特性。第三章详细介绍了Go语言中的goroutine和channel,这是Go语言并发编程的核心机制。作者通过生动的示例代码和实际应用案例,向读者展示了如何使用goroutine和channel实现并发编程。 第四章和第五章分别介绍了Go语言中的面向对象编程和函数式编程。通过深入讲解Go语言中的结构体、接口和函数,读者能够更好地应用这些特性进行代码设计和开发。 最后几章则介绍了Go语言中常用的库函数和工具。例如,第六章介绍了Go语言中用于网络编程的net包和http包。读者可以学习到如何使用这些库函数构建基于网络的应用程序。 总的来说,《Go语言学习笔记.pdf》是一本非常实用的Go语言学习资料。通过阅读这本书,读者能够系统地学习和理解Go语言的基本概念和高级特性,为之后的Go语言开发打下坚实的基础。无论是初学者还是有一定编程经验的开发者,都能从中获得丰富的知识和经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值