Go 언어로 된 채널에 대한 자세한 소개

풀어 주다: 2019-11-25 16:29:51
앞으로
3979명이 탐색했습니다.

Go 언어로 된 채널에 대한 자세한 소개

이 글은 go 언어의 채널에 관한 글입니다. 채널은 동시에 Go 동시성을 위한 중요한 지원 포인트라고 느끼기 때문입니다. 메모리를 공유하는 대신 메시지 전달 공유 메모리를 사용하여 통신합니다.

동시 프로그래밍은 매우 좋지만 동시성은 매우 복잡합니다. 다양한 프로그램 간의 통신을 어떻게 처리할지가 매우 중요합니다. 채널의 사용법과 특성에 대해 글을 쓰기 전에 운영 체제의 프로세스 간 통신을 검토해야 합니다.

프로세스 간 통신

엔지니어링에는 공유 데이터와 메시지라는 두 가지 일반적인 통신 모델이 있습니다. 이름에서 알 수 있듯이 프로세스 통신은 프로세스 간의 정보 교환을 의미합니다. 프로세스의 상호 배제 및 동기화에는 프로세스 간의 정보 교환이 필요하기 때문입니다. 통신 및 고급 프로세스 통신은 기본적으로 위의 모든 것이 고급 프로세스 통신입니다. 그중 고급 통신 메커니즘은 메시지 전달 시스템, 공유 메모리 시스템, 파이프라인 통신 시스템 및 클라이언트 서버 시스템으로 나눌 수 있습니다.

1. 메시지 전달 시스템

시스템에서 제공하는 통신 기본 요소를 사용하여 형식화된 메시지를 단위로 사용하므로 매우 비효율적입니다.
2. 공유 메모리 시스템

통신 프로세스는 저장 영역이나 데이터 구조를 공유합니다. 이 방법은 특정 파일을 캐리어로 사용하는 것과 같이 비교적 일반적입니다.

3. 클라이언트 서버 시스템

여러 다른 통신 메커니즘은 기본적으로 동일한 컴퓨터에 있습니다(동일한 환경이라고 할 수 있음). 물론 경우에 따라 컴퓨터 간 통신이 가능합니다. 클라이언트-서버 시스템은 IP 요청으로 간주될 수 있으며 클라이언트가 서버에 연결하도록 요청하는 것으로 이해됩니다.

이 방법은 이제 인터넷에서 더 많이 사용됩니다. RPC(매우 고급스러워 보이지만 오랫동안 운영 체제에서 사용 가능) 및 소켓과 같은 원격 예약이 더 일반적으로 사용됩니다. 많은 서비스에 RPC 호출이 필요하다는 것을 알 수 있기 때문에 매우 일반적으로 사용되며 프로그래밍과 밀접하게 관련되어 있습니다.

4. 파이프라인 통신부

마지막으로 파이프 통신의 메커니즘에 대해 자세히 설명하겠습니다. 운영 체제 수준에서 파이프는 읽기 프로세스와 쓰기 프로세스를 연결하는 데 사용되는 파일을 의미합니다. 그들을. 시스템에서는 파이프 파일이라고 합니다.

구현된 메커니즘은 다음과 같습니다: 파이프는 다음 두 가지 기능을 제공합니다

1. 프로세스가 파이프 파일에 대해 읽기 또는 쓰기 작업을 수행하는 경우 다른 프로세스는 기다리거나 차단하거나 절전 모드로 전환해야 합니다.

2. 쓰기(입력) 프로세스가 파이프 파일을 쓸 때 읽기(출력) 프로세스가 데이터를 가져온 후 깨어날 때까지 기다리거나 차단하거나 절전 모드로 전환됩니다. 파일을 파이프하면 쓰기 프로세스가 파이프에 쓴 후 깨어날 때까지 대기, 차단 또는 절전 모드도 수행됩니다.

channel

의 사용은 go의 채널에 해당하며, 이는 네 번째 유형이어야 합니다. go 언어의 채널은 언어 수준에서 제공되는 고루틴 간의 통신 방법입니다. 고루틴과 함께 사용하면 효과적이기 때문에 채널만 이야기하는 것은 의미가 없습니다. 먼저 일반 언어에서 프로그램 간 공유 메모리를 해결하는 방법을 살펴보겠습니다.

다음은 우리에게 친숙한 프로그램입니다.

package main

import "fmt"

var counts int = 0

func Count() {
    counts++
    fmt.Println(counts)
}
func main() {

    for i := 0; i < 3; i++ {
        go Count()
    }
}
로그인 후 복사

go를 배운 사람이라면 그 이유를 알 것입니다. 왜냐하면 Go 프로그램은 main() 메서드와 패키지를 초기화한 다음 main() 함수를 실행하지만, main() 함수는 When을 반환하며, 프로그램은 종료되고 메인 프로그램은 다른 고루틴을 기다리지 않으므로 결과가 출력되지 않습니다.

기존 언어가 이 동시성 문제를 어떻게 해결하는지 살펴보겠습니다.

package main

import "fmt"
import "sync"
import "runtime"

var counts int = 0

func Count(lock *sync.Mutex) {
    lock.Lock()
    counts++
    fmt.Println(counts)
    lock.Unlock()
}
func main() {
    lock := &sync.Mutex{}

    for i := 0; i < 3; i++ {
        go Count(lock)
    }

    for {
        lock.Lock()
        c := counts
        lock.Unlock()

        runtime.Gosched()

        if c >= 3 {
            break
        }

    }
}
로그인 후 복사

해결책은 약간 우스꽝스럽습니다. 여러 개의 잠금을 추가하는 것인데, 그 이유는 실행이 다음과 같기 때문입니다. 작업이 완료된 후에는 먼저 잠금을 해제해야 합니다. 메인 함수에서는 for 루프를 사용하여 카운터 값을 지속적으로 확인해야 합니다.

값이 3에 도달하면 이때 모든 고루틴이 실행되었다는 의미이며, 메인 함수가 반환되고 프로그램이 종료됩니다. 이 방법은 대중적인 언어에서 동시성을 해결하기 위해 선호되는 방법입니다. 프로젝트가 구체화되기 시작하면 얼마나 많은 잠금을 추가해야 할지 모르겠습니다.

채널이 이 문제를 어떻게 해결하는지 살펴보겠습니다.

package main

import "fmt"

var counts int = 0

func Count(i int, ch chan int) {
    fmt.Println(i, "WriteStart")
    ch <- 1
    fmt.Println(i, "WriteEnd")
    fmt.Println(i, "end", "and echo", i)
    counts++
}

func main() {
    chs := make([]chan int, 3)
    for i := 0; i < 3; i++ {
        chs[i] = make(chan int)
        fmt.Println(i, "ForStart")
        go Count(i, chs[i])
        fmt.Println(i, "ForEnd")
    }

    fmt.Println("Start debug")
    for num, ch := range chs {
        fmt.Println(num, "ReadStart")
        <-ch
        fmt.Println(num, "ReadEnd")
    }

    fmt.Println("End")

    //为了使每一步数值全部打印
    for {
        if counts == 3 {
            break
        }
    }
}
로그인 후 복사

고루틴 실행 단계와 채널의 특성을 명확하게 확인하기 위해 각 단계를 특별히 인쇄했습니다. 다음은 관심 있는 학생들이 직접 시도해 볼 수 있습니다. 인쇄 순서는 다를 수 있습니다.

Go 언어로 된 채널에 대한 자세한 소개

이 프로세스를 분석하고 채널의 역할을 살펴보겠습니다. 기본 프로그램이 시작됩니다.

"0 ForStart 0 ForEnd"를 인쇄하면 i = 0이며 이 루프가 실행되고 첫 번째 고루틴이 시작되었음을 나타냅니다.

打印 "1 ForStart"、"1 ForEnd"、"2 ForStart"、"2 ForEnd" 说明3次循环都开始,现在系统中存在3个goroutine;

打印 "Start debug",说明主程序继续往下走了,

打印 "0 ReadStar"t ,说明主程序执行到for循环,开始遍历chs,一开始遍历第一个,但是因为此时 i = 0 的channel为空,所以该channel的Read操作阻塞;

打印 "2 WriteStart",说明第一个 i = 2 的goroutine先执行到Count方法,准备写入channel,因为主程序读取 i = 0 的channel的操作再阻塞中,所以 i = 2的channel的读取操作没有执行,现在i = 2 的goroutine 写入channel后下面的操作阻塞;

打印 "0 WriteEnd",说明 i = 0 的goroutine也执行到Count方法,准备写入channel,此时主程序 i = 0 的channel的读取操作被唤醒;

打印 "0 WriteEnd" 和 "0 end and echo 0" 说明写入成功;

打印 "0 ReadEnd",说明唤醒的 i = 0 的channel的读取操作已经唤醒,并且读取了这个channel的数据;

打印 "0 ReadEnd",说明这个读取操作结束;

打印 "1 ReadStart",说明 i = 1 的channel读取操作开始,因为i = 1 的channel没有内容,这个读取操作只能阻塞;

打印 "1 WriteStart",说明 i = 1 的goroutine 执行到Count方法,开始写入channel 此时 i = 1的channel读取操作被唤醒;

打印 "1 WriteEnd" 和 "1 end and echo 1" 说明 i = 1 的channel写入操作完成;

打印 "1 ReadEnd",说明 i = 1 的读取操作完成;

打印 "2 ReadStart",说明 i = 2 的channel的读取操作开始,因为之前已经执行到 i = 2 的goroutine写入channel操作,只是阻塞了,现在因为读取操作的进行,i = 2的写入操作流程继续执行;

打印 "2 ReadEnd",说明 i = 2 的channel读取操作完成;

打印 "End" 说明主程序结束。

此时可能你会有疑问,i = 2 的goroutine还没有结束,主程序为啥就结束了,这正好印证了我们开始的时候说的,主程序是不等待非主程序完成的,所以按照正常的流程我们看不到 i = 2 的goroutine的的完全结束,这里为了看到他的结束我特意加了一个 counts 计算器,只有等到计算器等于3的时候才结束主程序,接着就出现了打印 "2 WriteEnd" 和 "2 end and echo 2" 到此所有的程序结束,这就是goroutine在channel作用下的执行流程。

上面分析写的的比较详细,耐心看两遍基本上就明白了,主要帮助大家理解channel的写入阻塞和读入阻塞的应用。

基本语法

channel的基本语法比较简单, 一般的声明格式是:

var ch chan ElementType
로그인 후 복사

定义格式如下:

ch := make(chan int)
로그인 후 복사

还有一个最常用的就是写入和读出,当你向channel写入数据时会导致程序阻塞,直到有其他goroutine从这个channel中读取数据,同理如果channel之前没有写入过数据,那么从channel中读取数据也会导致程序阻塞,直到这个channel中被写入了数据为止

ch <- value    //写入
value := <-ch  //读取
로그인 후 복사

关闭channel

close(ch)
로그인 후 복사

判断channel是否关闭(利用多返回值的方式):

 b, status := <-ch
로그인 후 복사

带缓冲的channel,说起来也容易,之前我们使用的都是不带缓冲的channel,这种方法适用于单个数据的情况,对于大量的数据不太实用,在调用make()的时候将缓冲区大小作为第二个参数传入就可以创建缓冲的channel,即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞。

c := make(chan int, 1024)
로그인 후 복사

单项channel,单向channel只能用于写入或者读取数据。channel本身必然是同时支持读写的,否则根本没法用。所谓的单向channel概念,其实只是对channel的一种使用限制。单向channel变量的声明:

var ch1 chan int   // ch1是一个正常的channel
var ch2 <-chan int // ch2是单向channel,只用于读取int数据
로그인 후 복사

单项channel的初始化

ch3 := make(chan int)
ch4 := <-chan int(ch3) // ch4是一个单向的读取channel
로그인 후 복사

超时机制

超时机制其实也是channel的错误处理,channel固然好用,但是有时难免会出现实用错误,当是读取channel的时候发现channel为空,如果没有错误处理,像这种情况就会使整个goroutine锁死了,无法运行。

我找了好多资料和说法,channel 并没有处理超时的方法,但是可以利用其它方法间接的处理这个问题,可以使用select机制处理,select的特点比较明显,只要有一个case完成了程序就会往下运行,利用这种方法,可以实现channel的超时处理:

原理如下:我们可以先定义一个channel,在一个方法中对这个channel进行写入操作,但是这个写入操作比较特殊,比如我们控制5s之后写入到这个channel中,这5s时间就是其他channel的超时时间,这样的话5s以后如果还有channel在执行,可以判断为超时,这是channel写入了内容,select检测到有内容就会执行这个case,然后程序就会顺利往下走了。

实现如下:

timeout := make(chan bool, 1)
go func() {
    time.Sleep(5s) // 等待s秒钟
    timeout <- true
}()

select {
    case <-ch:
    // 从ch中读取到数据
    case <-timeout:
    // 没有从ch中读取到数据,但从timeout中读取到了数据
}
로그인 후 복사

推荐:go语言教程

위 내용은 Go 언어로 된 채널에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:cnblogs.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿