GO语言使用之goroutine(协程)

一、从需求引入Go协程

要求统计1-9000000000 的数字中,哪些是素数?
1、分析思路:
传统的方法,就是使用一个循环,循环的判断各个数是不是素数。
10000——100000差了5.02S
2、代码如下:

package utils

import (
    "time"
    "fmt"
)

// 判断素数(1~100)


func PrimeNumber() bool {
    falg := true
    var arr  [] int
    for i := 1; i < 200000; i++ {
        falg = numberIsPrime(i)
        if falg {
            arr=append(arr,i)
        }
    }
    // fmt.Println("素数arr=",arr)
    fmt.Println("素数arr长度",len(arr))
    return true
}


func test(n int,m int,arr  [] int)  {
    falg := true

    for i := n; i < m; i++ {
        falg = numberIsPrime(i)
        if falg {
            arr=append(arr,i)
        }
    }
}

// 判断一个数是否是素数?
// 即只能被1或者自身整除的自然数(不包括1),称为素数/质数。
func numberIsPrime(num int) bool{
    for i := 2; i < num; i++ {
        if (num % i == 0) {
            return false;
        }
    }
    return true;

}

func CodeTime()  {
    start :=time.Now().Unix()
    fmt.Println("start时间",start)
    PrimeNumber()
    end :=time.Now().Unix()
    fmt.Println("end时间",end)
    res := end -start
    fmt.Printf("时间差:%d \n",res)
}

3、测试时间如下
test.go

package utils

import (
    "testing"
)

func TestPrimeNumber(t *testing.T)  {
    // res :=PrimeNumber()
    res :=PrimeGoroutine()
    if res {
        t.Logf("测试成功")
    }

}

测试结果:
这里写图片描述
4、优化方案:
使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成,这时就会使用到goroutine,提高效率

二、goroutine-基本介绍

1、简述进程和线程说明
进程就是程序程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。
一个进程可以创建核销毁多个线程,同一个进程中的多个线程可以并发执行。
一个程序至少有一个进程,一个进程至少有一个线程
2、程序、进程和线程的关系示意图
这里写图片描述

3、并发和并行

多线程程序在单核上运行,就是并发
多线程程序在多核上运行,就是并行
示意图:
这里写图片描述

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

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

三、Go协程和Go主线程

1、基本介绍
Go主线程(有程序员直接称为线程/也可以理解成进程): 一个Go线程上,可以起多个协程,你可以这样理解,协程是轻量级的线程[编译器做优化]。

2、Go协程的特点

  • 有独立的栈空间
  • 共享程序堆空间
  • 调度由用户控制
  • 协程是轻量级的线程

四、快速入门的案列:

1、案例说明
请编写一个程序,完成如下功能:
在主线程(可以理解成进程)中,开启一个goroutine, 该协程每隔1秒输出 “hello,world”
在主线程中也每隔一秒输出”hello,golang”, 输出10次后,退出程序
要求主线程和goroutine同时执行.

2、主线程和协程执行流程图:
这里写图片描述

3、代码如下:

package main

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

/*
请编写一个程序,完成如下功能:
在主线程(可以理解成进程)中,开启一个goroutine, 该协程每隔1秒输出 "hello,world"
在主线程中也每隔一秒输出"hello,golang", 输出10次后,退出程序
要求主线程和goroutine同时执行.

*/

//编写一个函数,每隔1秒输出 "hello,world"
func test() {
    for i := 1; i <= 10; i++ {
        fmt.Println("tesst () hello,world " + strconv.Itoa(i))
        time.Sleep(time.Second)
    }
}

func main() {

    go test() // 开启了一个协程

    for i := 1; i <= 10; i++ {
        fmt.Println(" main() hello,golang" + strconv.Itoa(i))
        time.Sleep(time.Second)
    }
}

4、测试结果如下:
这里写图片描述

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

五、goroutine的调度模型

1、MPG模式基本介绍
这里写图片描述
M: 操作系统的主线程 (是物理线程)
P: 协程执行需要的上下文
G: 协程

2、MPG模式运行的状态1
这里写图片描述
1)当前程序有三个M, 如果三个M都在一个cpu运行,就是并发,如果在不同的cpu运行就是并行
2)M1,M2,M3正在执行一个G,M1的协程队列有三个,M2的协程队列有3个, M3协程队列有2个
3)从上图可以看到: Go的协程是轻量级的线程,是逻辑态的,Go可以容易的起上万个协程。
4)其它程序c/java的多线程,往往是内核态的,比较重量级,几千个线程可能耗光cpu

3、MPG模式运行的状态2
这里写图片描述
1)分成两个部分来看
2)原来的情况是 M0 主线程正在执行G0协程,另外有三个协程在队列等待
3)如果G0协程阻塞,比如读取文件或者数据库等
4)这时就会创建M1主线程(也可能是从已有的线程池中取出M1),并且将等待的3个协程挂到M1下开始执行, M0的主线程下的G0仍然执行文件io的读写。
5)这样的MPG调度模式,可以既让G0执行,同时也不会让队列的其它协程一直阻塞,仍然可以并发/并行执行。
6)等到G0不阻塞了,M0会被放到空闲的主线程继续执行(从已有的线程池中取),同时G0又会被唤醒。

六、设置Golang运行的cpu数

基本介绍:
为了充分了利用多cpu的优势,在Golang程序中,设置运行的cpu数目。

func CpuDemo()  {
    //获取当前系统cpu数量
    cpuNum := runtime.NumCPU()
    fmt.Println("cpuNum=", cpuNum)

    //可以自己设置使用多个cpu,设置num-1的cpu运行程序
    runtime.GOMAXPROCS(cpuNum - 1)
    fmt.Println("ok")
}

go1.8后,默认让程序运行在多个核上,可以不用设置了
go1.8前,还是要设置一下,可以更高效的利益cpu

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值