需求:
要求统计 1~9000000000 的数字中,哪些是素数?
分析思路:
(1).传统的方法,就是使用一个循环,循环的判断各个数是不是素数.(很慢)
(2).使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成,这时就会使用到goroutine(速度提高4倍)
(1).进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
(2).线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位
(3).一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行
(4).一个程序至少有一个进程,一个进程至少有一个线程
4.并行和并发(1).多线程程序在单核上运行,就是并发
(2).多线程程序在多核上运行,就是并行
小结:并发:
因为是在一个 cpu上,比如有 10 个线程,每个线程执行10毫秒(进行轮询操作),从人的角度看,好像这10个线程都在运行,但是从微观的角度看,在某一个时间点看,其实只有一个线程在执行,这就是并发
并行:
因为是在多个 cpu上(比如有10个cpu),比如有 10 个线程,每个线程执行10毫秒(各自在不同cpu上执行),从人的角度看,这10个线程都在运行,但是从微观的角度看,在某一个时间点看,也同时有10个线程在执行,这就是并行
go主线程(有程序员直接称为线程,也可以理解成进程),一个go线程上,可以起多个协程,你可以这样理解,协程是轻量级的线程[编译器做优化]
go协程的特点
(1).有独立的栈空间
(2).共享程序堆空间
(3).调度由用户控制
(4).协程是轻量级的线程

案例说明:
请编写一个程序,完成如下功能:
(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协程同时执行


(1).主线程是一个物理线程,直接作用在cpu上的,是重量级的,非常耗费cpu资源
(2).协程从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对小
(3).Golang 的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显 Golang 在并发上的优势了

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")
- }
需求: 现在要计算 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)
- }
- }
(1).全局变量的互斤锁
(2).使用管道channel来解决
代码改进:
- 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()
- }