大道如青天,协程来通信,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang通道channel的使用EP14

简介: 众所周知,Go lang的作用域相对严格,数据之间的通信往往要依靠参数的传递,但如果想在多个协程任务中间做数据通信,就需要通道(channel)的参与,我们可以把数据封装成一个对象,然后把这个对象的指针传入某个通道变量中,另外一个协程从这个通道中读出变量的指针,并处理其指向的内存对象。

众所周知,Go lang的作用域相对严格,数据之间的通信往往要依靠参数的传递,但如果想在多个协程任务中间做数据通信,就需要通道(channel)的参与,我们可以把数据封装成一个对象,然后把这个对象的指针传入某个通道变量中,另外一个协程从这个通道中读出变量的指针,并处理其指向的内存对象。

通道的声明与创建

  

package main  
  
import "fmt"  
  
func main() {  
    var a chan int  
    if a == nil {  
        fmt.Println("通道是空的, 不能使用,需要先创建通道")  
        a = make(chan int)  
        fmt.Printf("数据类型是: %T", a)  
    }  
}

这里注意,通道声明之后还需要进行创建。

也可以通过海象操作符声明并创建:

package main  
  
import "fmt"  
  
func main() {  
      
    a := make(chan int)  
  
    fmt.Printf("数据类型是: %T", a)  
  
}

程序返回:

数据类型是: chan int%

如此,一个类型为整形的通道就创建好了。

此外,通道是引用数据类型:

package main  
  
import (  
    "fmt"  
)  
  
func main() {  
    ch1 := make(chan int)  
    fmt.Printf("%T,%p\n", ch1, ch1)  
  
    test1(ch1)  
  
}  
  
func test1(ch chan int) {  
    fmt.Printf("%T,%p\n", ch, ch)  
}

程序返回:

chan int,0x1400010e060  
chan int,0x1400010e060

可以看到,在test1函数内和main函数内通道的地址是一样的,所以他们指向的都是同一个通道。

通道的使用

通道创建之后,即可以在协程之间充当桥梁:

package main  
  
import "fmt"  
  
func job(ch1 chan int) {  
  
    ch1 <- 1  
  
}  
  
func main() {  
  
    ch1 := make(chan int)  
  
    fmt.Println(ch1)  
  
    go job(ch1)  
  
    data := <-ch1 // 从ch1通道中读取数据  
    fmt.Println("data-->", data)  
    fmt.Println("main。。over。。。。")  
}

这里我们声明一个函数job,把通道作为参数传递进去,注意这里参数类型除了声明通道本身以外,还得声明通道具体的数据类型。

随后在main函数中,可以理解为主协程,创建通道ch1,执行开启协程任务job,在job函数内,往通道内传递数字1

接着,主协程获取通道内由job协程传递的数据:

0x1400006a060  
data--> 1  
main。。over。。。。

藉此,就完成了数据的传递。

这里需要注意通道的调用语法:

data := <- a // 读取通道  
a <- data // 写入通道

同步阻塞

这里需要注意的是,通道无论是写入还是读取,都是同步阻塞机制。即当有协程对通道进行操作的时候,其他协程都处于“等待”状态,说白了,就是在“排队”,在之前的一篇:并发与并行,同步和异步,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang并发编程之GoroutineEP13,我们要么通过sync.WaitGroup来阻塞主协程,或者通过time.Sleep(time.Second)方法来阻塞,就是怕主协程提前执行完,早成子协程来不及执行。

而通道的出现,就间接帮我们实现了“阻塞”主协程的目的。

比如,多个协程任务操作一个变量:

package main  
  
import (  
    "fmt"  
)  
  
func job1(number int, squareop chan int) {  
    sum := 20  
    sum += number  
    squareop <- sum  
}  
  
func job2(number int, cubeop chan int) {  
    sum := 10  
    sum += number  
    cubeop <- sum  
}  
func main() {  
    number := 0  
    ch1 := make(chan int)  
    ch2 := make(chan int)  
    go job1(number, ch1)  
    go job2(number, ch2)  
    num1, num2 := <-ch1, <-ch2  
    fmt.Println("Final output", num1+num2)  
}

这里job1和job2两个协程任务同时异步执行,操作number变量,累加后往通道中写入,程序返回:

Final output 30

理论上,如果是并发执行,返回值应该是20或者10,但由于通道的存在,造成协程任务阻塞,变回了同步执行,所以返回了30。

同时,我们需要注意死锁问题,如果一个协程任务在一个通道上发送数据,那么其他的协程任务应该接收数据,如果这种情况不发生,那么程序将在运行时出现死锁。

换句话说,你发送了,就得有人接收,只发不接,或者只收不发,都会变成死锁。

此外,协程任务可以通过close(ch)方法来关闭通道:

package main  
  
import (  
    "fmt"  
)  
  
func job(ch1 chan int) {  
    // 发送方:3条数据  
    for i := 0; i < 3; i++ {  
        ch1 <- i //将i写入通道中  
    }  
    close(ch1) //将ch1通道关闭了。  
}  
  
func main() {  
    ch1 := make(chan int)  
    go job(ch1)  
    /*  
        子goroutine,写出数据3个  
                每写一个,阻塞一次,主程序读取一次,解除阻塞  
  
        主goroutine:循环读  
                每次读取一个,堵塞一次,子程序,写出一个,解除阻塞  
  
        发送发,关闭通道的--->接收方,接收到的数据是该类型的零值,以及false  
    */  
    //主程序中获取通道的数据  
    for {  
  
        v, ok := <-ch1 //其他goroutine,显示的调用close方法关闭通道。  
        if !ok {  
            fmt.Println("已经读取了所有的数据,", ok)  
            break  
        }  
        fmt.Println("取出数据:", v, ok)  
    }  
  
    fmt.Println("main...over....")  
}

这里将0到2写入chl通道,然后关闭通道。主函数里有一个死循环。类似while,它轮询通道是否在发送数据后,使用变量ok进行判断。如果ok是假的,则意味着通道关闭,因此循环结束,否则将会继续进行无限轮询。

select关键字

select 是 Go lang里面的一个流程控制结构,和switch关键字差不多,但是select会随机执行一个可运行的通道通信,如果没有通道通信可运行,它将阻塞,直到有通道通信可运行:

package main  
  
import (  
    "fmt"  
    "time"  
)  
  
func job(ch1 chan int) {  
  
    time.Sleep(2 * time.Second)  
    ch1 <- 200  
  
}  
  
func main() {  
  
    ch1 := make(chan int)  
    ch2 := make(chan int)  
  
    go job(ch1)  
    go job(ch2)  
  
    select {  
    case num1 := <-ch1:  
        fmt.Println("ch1中取数据。。", num1)  
    case num2, ok := <-ch2:  
        if ok {  
            fmt.Println("ch2中取数据。。", num2)  
        } else {  
            fmt.Println("ch2通道已经关闭。。")  
        }  
  
    }  
}

这里select会随机选择一个可运行的通道通信逻辑,可能是ch1通道,也有可能是ch2通道:

➜  mydemo git:(master) ✗ go run "/Users/liuyue/wodfan/work/mydemo/hello.go"  
ch1中取数据。。 200  
➜  mydemo git:(master) ✗ go run "/Users/liuyue/wodfan/work/mydemo/hello.go"  
ch1中取数据。。 200  
➜  mydemo git:(master) ✗ go run "/Users/liuyue/wodfan/work/mydemo/hello.go"  
ch2中取数据。。 200  
➜  mydemo git:(master) ✗

结语

综上,Golang的通道其实就是将协程任务进行隔离,编写并发逻辑时,关注通道即可,说白了,Golang的通道就是Python多进程通信中的管道,Golang虽然没有显性的多进程调用,但其协程调度底层就是多进程之间的通信,因为只有多进程才可能利用CPU的多核资源。

相关文章
|
9天前
|
JSON 人工智能 Go
在Golang中序列化JSON字符串的教程
在Golang中,使用`json.Marshal()`可将数据结构序列化为JSON格式。若直接对JSON字符串进行序列化,会因转义字符导致错误。解决方案包括使用`[]byte`或`json.RawMessage()`来避免双引号被转义,从而正确实现JSON的序列化与反序列化。
|
3月前
|
存储 算法 物联网
解析局域网内控制电脑机制:基于 Go 语言链表算法的隐秘通信技术探究
数字化办公与物联网蓬勃发展的时代背景下,局域网内计算机控制已成为提升工作效率、达成设备协同管理的重要途径。无论是企业远程办公时的设备统一调度,还是智能家居系统中多设备间的联动控制,高效的数据传输与管理机制均构成实现局域网内计算机控制功能的核心要素。本文将深入探究 Go 语言中的链表数据结构,剖析其在局域网内计算机控制过程中,如何达成数据的有序存储与高效传输,并通过完整的 Go 语言代码示例展示其应用流程。
77 0
|
8月前
|
存储 Go 开发者
Go语言中的并发编程与通道(Channel)的深度探索
本文旨在深入探讨Go语言中并发编程的核心概念和实践,特别是通道(Channel)的使用。通过分析Goroutines和Channels的基本工作原理,我们将了解如何在Go语言中高效地实现并行任务处理。本文不仅介绍了基础语法和用法,还深入讨论了高级特性如缓冲通道、选择性接收以及超时控制等,旨在为读者提供一个全面的并发编程视角。
173 50
|
8月前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
8月前
|
安全 Go 调度
探索Go语言的并发模型:goroutine与channel
在这个快节奏的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你深入了解Go语言的goroutine和channel,这两个核心特性如何协同工作,以实现高效、简洁的并发编程。
|
8月前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
|
9月前
|
安全 Go 调度
探索Go语言的并发之美:goroutine与channel
在这个快节奏的技术时代,Go语言以其简洁的语法和强大的并发能力脱颖而出。本文将带你深入Go语言的并发机制,探索goroutine的轻量级特性和channel的同步通信能力,让你在高并发场景下也能游刃有余。
|
9月前
|
存储 安全 Go
探索Go语言的并发模型:Goroutine与Channel
在Go语言的多核处理器时代,传统并发模型已无法满足高效、低延迟的需求。本文深入探讨Go语言的并发处理机制,包括Goroutine的轻量级线程模型和Channel的通信机制,揭示它们如何共同构建出高效、简洁的并发程序。
|
8月前
|
数据采集 缓存 程序员
python协程使用教程
1. **协程**:介绍了协程的概念、与子程序的区别、优缺点,以及如何在 Python 中使用协程。 2. **同步与异步**:解释了同步与异步的概念,通过示例代码展示了同步和异步处理的区别和应用场景。 3. **asyncio 模块**:详细介绍了 asyncio 模块的概述、基本使用、多任务处理、Task 概念及用法、协程嵌套与返回值等。 4. **aiohttp 与 aiofiles**:讲解了 aiohttp 模块的安装与使用,包括客户端和服务器端的简单实例、URL 参数传递、响应内容读取、自定义请求等。同时介绍了 aiofiles 模块的安装与使用,包括文件读写和异步迭代
259 0
|
9月前
|
存储 Go 调度
深入理解Go语言的并发模型:goroutine与channel
在这个快速变化的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你穿越Go语言的并发世界,探索goroutine的轻量级特性和channel的同步机制。摘要部分,我们将用一段对话来揭示Go并发模型的魔力,而不是传统的介绍性文字。

热门文章

最新文章

推荐镜像

更多