zoukankan      html  css  js  c++  java
  • GoRoutine协程间通信

    原发于taskhub

    goroutine是Golang原生支持并发的基础,也是go语言中最基本的执行单元,它具有如下的特性:

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

    在使用goroutine进行并发编程时,往往会遇到协程先后、交替执行的问题,此时可使用go语言中专有的数据结构chan(管道)进行协程间的通信,其中又可分为如下几个具体情形:

    1. 无缓冲--单个channel变量

    func groutine3(ch1 chan bool, e chan bool) {
    	for i := 1; i <= POOL; i++ {
    		fmt.Println("A",i)
    		ch1 <- true
    		if i%2 == 1 {
    			fmt.Println("groutine-1:", i)
    		}
    	}
    	e <- true
    }
    
    func groutine4(ch1 chan bool) {
    	for i := 1; i <= POOL; i++ {
    		fmt.Println("B",i)
    		<-ch1
    		if i%2 == 0 {
    			fmt.Println("groutine-2:", i)
    		}
    	}
    }
    
    func main() {
    	ch1 := make(chan bool)
    	exit := make(chan bool)
    	go groutine3(ch1,exit)
    	go groutine4(ch1)
    	<-exit
    	time.Sleep(time.Second * 1)
    }
    
    • 协程3作为生产者,负责给chan写入数据,写入数据后,未立即停止并阻塞协程,而是继续向下执行;等到第二次向chan写入数据时,若发现chan的数据未被读取,则阻塞等待;
    • 协程4作为消费者,需要读取chan内的数据;若在读取时发现chan为空,则阻塞协程并等待,直到chan被写入数据才继续向下执行
    • 无缓存模式下,chan的数据容量默认为1,即只能传入一个数据
    • channel是同步阻塞的
    • 上述半开半闭方式的协程交替执行会出现先后顺序混乱BAABBAAB,仅考虑起始条件而忽略了终止条件
    • 协程4开启->打印B->读取阻塞->协程3开启->打印A->写入chan->写入未阻塞->继续打印A->写入阻塞->协程4开启->打印B->读取chan->打印B->读取阻塞
    • goroutine的执行顺序不是代码行编写的前后顺序,而是按照一定规则

    2. 无缓冲--两个channel变量

    func groutine1(ch1 chan bool, ch2 chan bool, e chan bool) {
    	for i := 1; i <= POOL; i++ {
    		ch1 <- true
    		fmt.Println("A1",i)
    		if i%2 == 1 {
    			fmt.Println("groutine-1:", i)
    		}
    		fmt.Println("A2",i)
    		<- ch2
    		fmt.Println("A3",i)
    	}
    	e <- true
    }
    
    func groutine2(ch1 chan bool, ch2 chan bool) {
    	for i := 1; i <= POOL; i++ {
    		<-ch1
    		fmt.Println("B1",i)
    		if i%2 == 0 {
    			fmt.Println("groutine-2:", i)
    		}
    		fmt.Println("B2",i)
    		ch2 <- true
    		fmt.Println("B3",i)
    	}
    }
    func main() {
    	ch1 := make(chan bool)
    	ch2 := make(chan bool)
    	exit := make(chan bool)
    	go groutine1(ch1,ch2,exit)
    	go groutine2(ch1,ch2)
    	<-exit
    	time.Sleep(time.Second * 1)
    }
    
    • 生产者-消费者模式
    • 循环体首尾阻塞独占模式,两个chan交替释放控制权
    • 协程1向ch1写入数据后,执行后续代码段,待到读取ch2的数据时发生阻塞,协程1阻塞等待
    • 协程2读取ch1数据,执行后续代码段,然后向ch2写入数据,并再进入一次for循环,但此次被阻塞在ch1的读取阶段
    • 协程2阻塞后,轮到协程1执行,协程1在等待ch2被写入数据后,开始执行后续代码
    • 完成了两个协程的交替运行,且实现了并发安全

    3. 缓冲模式

    缓冲区可以存储10个int类型的整数,在执行生产者线程的时候,线程就不会阻塞,一次性将10个整数存入channel,在读取的时候,也是一次性读取。

    package main
    
    // 带缓冲区的channel
    
    import (
    	"fmt"
    	"time"
    )
    
    func produce(ch chan<- int) {
    	for i := 0; i < 10; i++ {
    		ch <- i
    		fmt.Println("Send:", i)
    	}
    }
    
    func consumer(ch <-chan int) {
    	for i := 0; i < 10; i++ {
    		v := <-ch
    		fmt.Println("Receive:", v)
    	}
    }
    
    func main() {
    	ch := make(chan int, 10)
    	go produce(ch)
    	go consumer(ch)
    	time.Sleep(1 * time.Second)
    }
    

    4. 总结

    • 在有缓冲模式下,可以在主线程中先写后读,无需新开协程负责读取
    • 在无缓冲模式下,写入数据的同时必须有协程在等待读取管道中的数据,否则将抛出死锁的错误
    • ch := make(chan int,1) ch := make(chan int) 在使用上完全不一样
    • 无缓冲模式是同步阻塞,有缓冲模式是异步执行
  • 相关阅读:
    【科普杂谈】计算机按下电源后发生了什么
    【VS开发】使用WinPcap编程(1)——获取网络设备信息
    【VS开发】使用WinPcap编程(1)——获取网络设备信息
    微信公众平台消息接口PHP版
    编码gbk ajax的提交
    mysql 查询
    js cookie
    js同域名下不同文件下使用coookie
    去掉A标签的虚线框
    jquery切换class
  • 原文地址:https://www.cnblogs.com/litchi99/p/13724821.html
Copyright © 2011-2022 走看看