美文网首页Go语言实践Go
Go语言学习笔记19.并发编程

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

作者: 快乐的提千万 | 来源:发表于2019-11-07 10:46 被阅读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 //读

相关文章

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

    前置知识点:(如果不了解建议百度下再学并发编程) 并发、并行,同时进行叫并行,交替进行叫并发(时间片轮循)。 进程...

  • Go基础语法(九)

    Go语言并发 Go 是并发式语言,而不是并行式语言。 并发是指立即处理多个任务的能力。 Go 编程语言原生支持并发...

  • Go并发

    Go语言中的并发编程 并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很...

  • 笨办法学golang(二)

    这是Go语言学习笔记的第二篇文章。 Go语言学习笔记参考书籍「Go语言编程」、Go官方标准库 前文提要 上篇文章中...

  • Go并发调度

    本文是《循序渐进Go语言》的第六篇-Go并发调度。本文是学习《Go语言学习笔记》的并发调度一章,然后结合阅读源码的...

  • go语言开发培训班哪里好

    Go作为专门为并发和大数据设计的语言,在编程界占据越来越重要的地位!越来越多的人开始学习go编程语言,go语言开发...

  • Go语言简介

    Go语言简介 Go语言设计的初衷 针对其他语言的痛点进行设计并加入并发编程为大数据,微服务,并发而生的通用编程语言...

  • Go 语言学习笔记-并发编程

    Go 并发编程 概述 简而言之,所谓并发编程是指在一台处理器上“同时”处理多个任务。 宏观的并发:是指在一段时间内...

  • 16.并发

    并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因。 Go语言...

  • Go语言基础之并发

    并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因。 Go语言...

网友评论

    本文标题:Go语言学习笔记19.并发编程

    本文链接:https://www.haomeiwen.com/subject/lskebctx.html