Go语言实践Go

Go语言学习笔记19.并发编程

2019-11-07  本文已影响0人  快乐的提千万

前置知识点:(如果不了解建议百度下再学并发编程)

协程 goroutine

从创建的消耗来比较,进程>线程>协程。
有很多时候我们只是想多个座位,结果却不得不去盖一栋房子。而协程就是这个凳子,进程就是房子。而进程和线程的消耗差距比这个要大几千倍。

创建

go func(){
}
package main

import (
    "fmt"
    "time"
)

func newTask() {
    index := 1
    fmt.Println("协程创建了")
    for {
        time.Sleep(time.Second * 2) //延时2s
        fmt.Println("协程 index",index)
        index ++
    }
}

func main() {

    go newTask() //新建一个协程, 新建一个任务
    index := 1
    //死循环
    for {
        time.Sleep(time.Second) //延时1s
        fmt.Println("主进程 index",index)
        index ++
    }
}

输出:

协程创建了
主进程 index 1
协程 index 1
主进程 index 2
主进程 index 3
协程 index 2
主进程 index 4
主进程 index 5
协程 index 3
主进程 index 6
主进程 index 7
协程 index 4
主进程 index 8
...

和创建子进程或子线程是一样的逻辑,互不干扰。
当主进程或主协程停止的时候,子协程也会停止。

相关函数

  1. runtime.Gosched() 让出时间片
    go func() {
        for i := 0; i < 5; i++ {
            fmt.Println("go")
        }
    }()

    for i := 0; i < 2; i++ {
        //让出时间片,先让别的协议执行,它执行完,再回来执行此协程
        runtime.Gosched()
        fmt.Println("hello")
    }
  1. runtime.Goexit() 终止当前 goroutine 执⾏,调度器确保所有已注册 defer延迟调用被执行。
package main

import (
    "fmt"
    "runtime"
)

func test() {
    defer fmt.Println("协程结束")

    //return //终止此函数
    runtime.Goexit() //终止所在的协程

    fmt.Println("这句话不会被打印")
}

func main() {

    //创建新建的协程
    go func() {
        fmt.Println("协程开始前")

        //再创建一个
        test()

        runtime.Gosched()
        fmt.Println("协程创建了")
    }() //别忘了()
    
    //死循环
    for  {}
}
  1. runtime.GOMAXPROCS() 设置可以并行计算的CPU核数的最大值,并返回之前的值。
    比如有两个协程,设置1,就会交替执行,如果设置2,就会一起执行。
    n := runtime.GOMAXPROCS(2) //指定以1核运算
    //n := runtime.GOMAXPROCS(4) //指定以4核运算
    fmt.Println("n = ", n)

    for i:=1;i<100000;i++{
        go fmt.Print(1)
        fmt.Print(0)
    }

从打印看效果不是很明显,但是可以用耗时任务来看。

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    n := runtime.GOMAXPROCS(2)
    fmt.Println("n = ", n)

    go func(){
        for { //抢光所有资源
        }   
    }()
    for i:=1;i<100000;i++{
        fmt.Print(0)
        time.Sleep(time.Second)
    }   
}

这个就很明显了,如果只用1核,最多打印一个0,然后sleep,资源就全部被协程抢了,再也打印不出来了。但是如果用2核,则可以打印出来。
有人可能会说,不是说交替执行么,但是这里面的协程是死循环,事情一直没有完成,而主进程的sleep会表示我还可以等,则CPU不会让出时间片。
如果都是死循环会怎么样?会交替执行,因为主进程说,我也有急事,CPU则会让他们各自执行一段时间。

channel

之前的进程、线程之间,会通过共享内存来通信,但是数据都是存放在各自的家里。但是协程则是通过通信来共享内存。
channel本意是管道,控制并发最好的方法是什么?排队啊!管道就是将所有人的任务按顺序执行。

创建:
make(chan Type, capacity) capacity表示缓存区大小
关闭:
close(chan Type)

package main

import (
    "fmt"
    "time"
)

//定义一个打印机,参数为字符串,按每个字符打印
//打印机属于公共资源
func Printer(str string) {
    for _, data := range str {
        fmt.Printf("%c", data)
        time.Sleep(time.Second)
    }
    fmt.Printf("\n")
}

func person1() {
    Printer("hello")
}

func person2() {
    Printer("world")
}

//全局变量,创建一个channel
var ch = make(chan int)

//person3执行完后,才能到person4执行
func person3() {
    Printer("HELLO")
    ch <- 666 //给管道写数据,发送
    close(ch)
}

func person4() {
    //从管道取数据,接收,如果通道没有数据他就会阻塞,所有前面的会先执行
    if num, ok := <-ch; ok == true {
        fmt.Println("num = ", num)
    } else { //管道关闭
        fmt.Println("管道关闭了")
    }
    Printer("WORLD")
}

func main() {
    //新建2个协程,代表2个人,2个人同时使用打印机
    //没有管道的情况,会抢  输出:hwoelrllod
    //go person1()
    //go person2()

    //有管道的情况,顺序执行: 
    /*
    HELLO
    num =  666
    WORLD
    */
    go person3()
    go person4()
    
    //特地不让主协程结束,死循环
    for {

    }
}

管道的缓冲
无缓冲,则必须要发送方和接收方同时在才可以继续,否则发送方发了一个后就会阻塞。
有缓存,在缓冲大小内,发送方可以一直发,直到缓冲区满,接收方什么时候来收都可以。

    //创建一个无缓存的channel
    ch := make(chan int, 0)
    //创建一个有缓存的channel
    //ch := make(chan int, 3)

    //len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小
    fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))

    //新建协程
    go func() {
        for i := 0; i < 3; i++ {
            fmt.Printf("子协程:i = %d\n", i)
            ch <- i //往chan写内容
        }
    }()

    //延时,没有缓存则发送方也会等待
    time.Sleep(2 * time.Second)

    for i := 0; i < 3; i++ {
        num := <-ch //读管道中内容,没有内容前,阻塞
        fmt.Println("num = ", num)
    }

管道的方向限定

    //创建一个channel, 双向的
    ch := make(chan int)

    //双向channel能隐式转换为单向channel
    var writeCh chan<- int = ch //只能写,不能读
    var readCh <-chan int = ch  //只能读,不能写
    
    writeCh <- 666 //写
    <-readCh //读
上一篇 下一篇

猜你喜欢

热点阅读