• Go 中channel/goroutine实现并发和并行


    1.为什么要使用goroutine

    可以让程序并发和并行的运行,增加程序运行速度

    并发: 多个线程同时竞争一个位置,竞争到才可以执行,每个时间段只有一个线程在执行

    并行:多个线程可以同时执行,每一个时间段,可以有多个线程同时执行。

    通俗来说多线程程序在单核CPU上运行是并发,多线程程序在多核CPU上运行就是并行,如果线程数大于CPU核数,则多线程程序在多个CPU上面运行既有并行又有并发

    2.Golang中的携程(goroutine)以及主线程

    golang 中的主线程:在一个Golang程序的主线程上可以启用多个协程。golang中多协程可以实现并行和并发。

    协程:可以理解为用户级线程,这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的。Golang的一个特色是从语言层面原生支持携程,在函数或者方法前面加Go关键词就可以创建一个协程。go的协程就是goroutine.

    主线程如果执行完毕后,想要等待携程完成,在退出。

    通过sync.WaitGroup 可以实现主线程等待协程执行完毕

    1. var wg sync.WaitGroup
    2. func test() {
    3. for i:= 0; i < 10; i++ {
    4. fmt.Println("test() 你好 gloang", i)
    5. }
    6. wg.Done() // 协程计数器加-1
    7. }
    8. func main(){
    9. wg.Add(1) // 协程计数器 +1
    10. go test() //表示开启一个协程
    11. for( i:= 0; i< 10; i++ {
    12. fmt.Println("main ",i)
    13. }
    14. wg.Wait() //等待协程计数器为0 退出
    15. }

    3.golang 并行运行的时候占用的CPU数量

    go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核的机器上,调度器会把Go代码同时调度到8个os线程。

    4.单个协程运行----素数判断

    1. package main
    2. import (
    3. "fmt"
    4. "time"
    5. )
    6. func main(){
    7. start := time.Now().Unix()
    8. for num:=2; num < 12000; num ++ {
    9. var flag = true
    10. for i:=2; i < num; i++ {
    11. if num%i == 0 {
    12. flag = false
    13. break
    14. }
    15. }
    16. if flag {
    17. fmt.Println(num,"是素数")
    18. }
    19. }
    20. end := time.Now().Unix()
    21. fmt.Println("程序运行总共用时:",end - start,"ms")
    22. }

    5. 多个协程进行统计素数

    1. package main
    2. import (
    3. "fmt"
    4. "time"
    5. "sync"
    6. )
    7. var wg sync.WaitGroup
    8. func test(n int) {
    9. for num := (n - 1)*30000 + 1; num < n * 30000; num ++ {
    10. var flag = true
    11. for i := 2; i < num; i++ {
    12. if num%i == 0{
    13. flag = false
    14. break
    15. }
    16. }
    17. if flag{
    18. }
    19. }
    20. wg.Done()
    21. }
    22. func main() {
    23. start := time.Now().Unix()
    24. for i := 1; i < 4; i++ {
    25. wg.Add(1)
    26. go test(i)
    27. }
    28. wg.Wait()
    29. end := time.Now().Unix()
    30. fmt.Println("全部执行完毕,共用时", end - start,"ms")
    31. }

    6.Channel 管道

             管道是Golang在语言级别上提供的goroutine间的通讯方式,我们可以使用channel在多个goroutine之间传递消息。如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通讯机制。

            Golang的并发模型是CSP提倡通过通讯共享内存而不是通过共享内存而实现通信。

            Go语言中的管道是一种特殊的类型。管道像是一个传送带或者队列,总是遵循先入先出的规则,保证收发数据的顺序。每一个管道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。

    1、channel 类型

    是一种引用类型

           var 变量 chan 元素类型

    例如:

            var ch1 chan int   //声明一个传递整型的管道

            var ch2 chan bool //声明一个传递布尔类型的管道

            var ch3 chan []int  //声明一个传递int 切片的管道

    2、创建channel

    声明的管道后需要使用make函数初始化之后才能使用。

    make( chan 元素类型, 容量)

    3、channel操作

    管道由发送send, 接收 receive, 关闭close 三种操作。

    发送和接收都使用 <- 符号

    发送(将数据放在管道内)

    将一个值发送到管道中

    ch <- 10 

    接收(从管道内取值)

    从一个管道中接收值

    b := <-ch  

     管道属于引用数据类型

    1. ch1 := make(chan int, 4)
    2. ch1 <- 34
    3. ch1 <- 54
    4. ch1 <- 64
    5. ch2 := ch1
    6. ch2 <- 25
    7. <-ch1
    8. <-ch1
    9. <-ch1
    10. d := <- ch1
    11. fmt.Println(d) // d表示25 证明管道类型是引用类型

    管道阻塞

    1. ch6 := make(chan int, 1)
    2. 第一种玩法
    3. ch6 <- 34
    4. ch6 <- 64
    5. //报错死锁,这就属于管道阻塞,传进来超容量数据
    6. 第二种玩法
    7. ch6 <- 34
    8. ch6 <- 64
    9. m1:= <-ch6
    10. m2:= <-ch6
    11. m3:= <-ch6
    12. //管道中如果没有值了还在取,就会出发阻塞了

    如果要循环读取管道,就需要close(ch)关闭管道,否则会造成死锁

    通过for 循环管道,管道可以不关闭,但是通过for .......range 获取读取管道,需要关闭

    7、单向管道

            我们会将管道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用管道都会对其进行限制,比如限制管道在函数中只能发送或只能接收。

    1. //单向管道 只写
    2. ch2 := make(chan<- int ,2)
    3. ch2 <- 10
    4. ch2 <- 12
    5. //单向管道 只读
    6. ch3 := make(<-chan int, 2)
    7. //双向管道
    8. ch4 := make(chan int, 2)

    用途: 可以在形参中定义,保证一个函数只能写,另一个函数只能读

    8、select 多路复用

            在某些场景下,我们需要同时对多个通道接收数据。这个时候我们就可以用到golang中给我们提供的select多路服用。

            通常情况通道在接收数据时,如果没有数据可以接收将会发生阻塞。

            这种方式虽然可以实现多个管道接收值的需求,但是运行性能会差很多。为了应对这种场景,Go内置了select 关键字,可以同时响应多个管道的操作。

            select 的使用类似于switch语句,他有一系列case分支和一个默认的分支。每个case会对应一个管道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。

    1. func main(){
    2. intChan := make(chan int,10)
    3. fot i:=0; i < 10; i++ {
    4. intChan <- i
    5. }
    6. stringChan := make(chan string,5)
    7. for i:=0; i < 10; i++ {
    8. stringChan <- "hello" +fmt.Sprintf("%d",i)
    9. }
    10. for{
    11. select{
    12. case v:= <- intChan:
    13. fmt.Printf("从intChan读取的数据%d\n",v)
    14. case v:= <- stringChan:
    15. fmt.Printf("从stringChan读取的数据%v\n",v)
    16. default:
    17. fmt.Printfln("数据获取完毕")
    18. return
    19. }
    20. }
    21. }

    9、goroutine 遇到异常如何处理

            在golang语言中,如果有多个协程的话,其中一个出问题,并不影响其他的协程进行工作。

            如果遇到报错的话,可以通过匿名函数和defer进行处理

    1. func test() {
    2. //处理协程异常情况
    3. defer func(){
    4. //捕获函数出现的panic
    5. if err := recover(); err != nil {
    6. fmt.Println("函数出现的错误",err)
    7. }
    8. }
    9. //协程操作
    10. 。。。。。
    11. 。。。。。
    12. }

    10、Golang并发安全和锁

            互斥锁:是传统并发编程中对共享资源进行访问控制的主要手段,它由标准库sync中的Mutex结构体类型表示。sync.Mutex类型只有两个公开的指针方法:lock和Unlock.lock锁定当前的共享资源,Unlock进行解锁

    1. var mutex sync.Mutex
    2. var count = 0
    3. func test() {
    4. mutex.lock()
    5. count++
    6. fmt.Println("the count is:",count)
    7. mutex.Unlock()
    8. }

  • 相关阅读:
    1273 - Unknown collation: ‘utf8mb4_0900_ai_ci‘, Time: 0.222000s 解决
    Scala基础教程--14--隐式转换
    跨域问题解决之隔山隔海都不怕
    【项目管理】项目中如何进行风险管理
    面试经典150题——Day23
    c# 一些警告的处理方法
    2023八股每日一题(九月份)
    网络舆情监测是干嘛的?
    网站服务器租用的类型有哪些
    INDEMIND:渗透率不足10%,扫地机器人为何成了“存量市场”?
  • 原文地址:https://blog.csdn.net/Xiao_qsn/article/details/127910461