• Golang学习之路6-goroutine并发



    前言

    什么是 goroutine?简称可以使:go程、并发

    • goroutine是与其他函数或方法同时运行的函数或方法。
    • goroutine可以被认为是轻量级线程,天生支持多并发。
    • 与线程相比,创建goroutine的成本很小,因此Go 应用程序通常会同时运行数千个goroutine。

    一、goroutine用法

    go语言天生支持多并发,使用方式:

    • 方式一:go func()
    • 方式二(匿名):go func(){}()
    package main
    import (
    	"fmt"
    	"time"
    )
    func display(count int) {
    	for {
    		fmt.Println("这是子go程======>:", count)
    		time.Sleep(1)
    		count++
    	}
    }
    
    func main() {
    	go display(1)
    
    	// 匿名 子go程 函数
    	//go func () {
    	//	count := 1
    	//	for {
    	//		fmt.Println("这是子go程======>:", count)
    	//		time.Sleep(1)
    	//		count++
    	//	}
    	//}()
    	
    	// 主go程
    	count := 1
    	for {
    		fmt.Println("这是主go程:", count)
    		count++
    		time.Sleep(1)
    		if count > 2 {
    			break
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    可以看到,主进程没有打印第3次,而子进程打印了第3次
    在这里插入图片描述

    二、goroutine循环

    比如起3个进程跑goroutine,cpu资源会被竞争不确定谁先谁后:

    for i := 0; i <= 3; i++ {
    	go func() {}()
    }
    
    • 1
    • 2
    • 3

    三、goroutine提前退出

    三种退出go程方式:

    • return:只退出当前子go程函数
    • runtime.Goexit():只退出当前go程
    • os.Exit(-1):退出所以的主子go程
    package main
    
    import (
    	"fmt"
    	"os"
    	"time"
    )
    
    func main() {
    	go func() {
    		func() {
    			fmt.Println("这是子go程 内部函数111")
    			//return 			  // 只结束此函数,会执行到:子go程222
    			//runtime.Goexit()    // 退出当前go程,不会执行到:子go程222
    			os.Exit(-1)	          // 退出所以主子进程
    		}()
    		fmt.Println("子go程222")
    	}()
    	time.Sleep(1)
    	fmt.Println("结束主go程!")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    四、goroutine双向管道

    通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

    ch <- value    // 把 value  发送到通道 ch
    value := <-ch  // 从 ch 接收数据,并把值赋给 value 
    
    • 1
    • 2

    chan双向管道:

    • 使用通讯时不需要上锁解锁;
    • 未创建容量时是无缓冲的,反之是有缓冲的;
    • 当缓冲区写满时或读取完成时会阻塞,当被读取后再恢复写入;
    • 管道的读与写次数必须对等,否则会被堵塞,①如果阻塞主go程那么程序被锁死至崩溃,②如果阻塞在子go程会出现内存泄漏;
    • 需要使用make分配空间,否则管道默认是nil,读写都会堵塞(和map一样所以建议都是make);
    • 使用goroutine管道时推荐使用for range,当管道写入完成后应关闭close(chan)
    package main
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	// 创建一个容量为10的int有缓冲管道
    	numChan := make(chan int, 10)
    
    	// 写“管道”数据
    	go func() {
    		for i := 1; i < 21; i++ {
    			numChan <- i
    			fmt.Println("11-->子go程写入数据:", i)
    		}
    		// 写入结束,关闭管道
    		close(numChan)
    	}()
    
    	// 读“管道”数据
    	go func() {
    		for num := range numChan {
    			fmt.Println("22-->子go程读取数据data:", num)
    		}
    	}()
    
    	time.Sleep(1 * time.Second)
    
    	// 判断管道是否关闭
    	for {
    		v, ok := <-numChan
    		if !ok {
    			fmt.Println("管道已经关闭,准备退出!!")
    			break
    		}
    		fmt.Println("v:", v)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    五、goroutine单向管道

    一般用于函数参数,比如只读、只写单向管道,看看下面和双向管道有何区别?
    生产者:chan<-
    消费者:<-chan
    双向管道时都是:<-numChan

    package main
    import (
    	"fmt"
    	"time"
    )
    
    // 生产者:只写入数据
    func producer(out chan<- int) {
    	for i := 1; i < 11; i++ {
    		out <- i
    		fmt.Println("写入数据:", i)
    	}
    	close(out)
    }
    
    // 消费者:只读数据
    func consumer(in <-chan int) {
    	for v := range in {
    		fmt.Println("读取数据:", v)
    	}
    }
    
    func main() {
    	// 创建一个双向通道
    	numChan := make(chan int, 5)
    
    	// 创建生产者
    	go producer(numChan)
    
    	// 创建消费者
    	go consumer(numChan)
    
    	time.Sleep(200)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    在这里插入图片描述

    六、监听管道

    在多go程并发时,我们可以通过select进行监听到对应数据,突然觉得类似消息事件一样,一个负责推、一个负责监听…
    select:监听多管道 (写入/读写数据、关闭管道)

    package main
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	// 创建两个管道,用来测试监听
    	chan1 := make(chan int)
    	chan2 := make(chan int)
    	// 监听go程
    	go func() {
    		for {
    			select {
    			case data1 := <-chan1:
    				fmt.Println("监听到chan111:", data1)
    			case data2 := <-chan2:
    				fmt.Println("监听到chan222:", data2)
    			default:
    				fmt.Println("正常监听中...")
    				time.Sleep(2 * time.Second)
    			}
    		}
    	}()
    
    	// go程:两个管道分别写入数据
    	go func() {
    		for i := 0; i < 5; i++ {
    			chan1 <- i
    			i++
    			chan2 <- i
    		}
    	}()
    
    	count := 1
    	for {
    		time.Sleep(10 * time.Second)
    		if count == 6 {
    			break
    		}
    		count++
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    如下图,可以看到当我们监听到有写入数据时会得到对应的类型数据,当没有写入时 default 一直在负责监听!
    在这里插入图片描述

    总结

    以上就是今天学习的内容,本文仅仅简单介绍了goroutine的使用,而在项目中如何实践还请大家多多查阅资料了解!

  • 相关阅读:
    如何远程访问Linux MeterSphere一站式开源持续测试平台
    (27)Blender源码分析之顶层菜单的关于对话框
    Linux:redis数据库源码包安装
    mybatis-plus-enum实现枚举类型自动转换
    jvm初识
    git submudles 代码如果提交到一个ID 上
    前端三剑客:html、css、javascript
    解析Spring Cloud面试的十道难题,帮你攻克技术关卡
    什么是GPT,初学者怎么使用并掌握Chat GPT工具
    线程池参数千万不要这样设置,坑得我整篇文章都写错了,要注意!
  • 原文地址:https://blog.csdn.net/qq_42675140/article/details/127819216