>백엔드 개발 >Golang >Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

Go语言进阶学习
Go语言进阶学习앞으로
2023-07-21 10:09:541496검색
채널이 필요한 이유

Go의 채널은 파이프라인이라고도 하며 멀티 스레드

. channel일반적으로 Go의 공유 데이터도 사용됩니다

    . 하지만 Go에서는 데이터를 공유하는 방법이 두 가지가 있습니다.
  • 공유 메모리는 소통을 가능하게 합니다.
  • 🎜채널을 통해 소통하세요(권장). 🎜🎜

공유 메모리 통신을 권장하지 않는 이유는 무엇인가요?

샘플 코드: 멀티스레딩은 값을 수정합니다.

Function

func Calc() {
    defer wg.Done()
    NUM = NUM - 1
}

main

var NUM = 100
var wg sync.WaitGroup


func main() {
    for i := 0; i<100;i++  {
        wg.Add(1)
        go Calc()
}
    wg.Wait()
    fmt.Println(NUM)
}

실행 결과

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

2입니다. 그렇죠? 하하하, 이론은 0아여야 합니다.

이게 왜죠?

이것이 공유 메모리가 권장되지 않는 이유입니다. 우리 코드는 이미 다중 스레드입니다.

첫 번째 함수 코드 3행 NUM = NUM - 1에 있습니다.

如果多个线程同时执行到这一行,并且没有加锁,就会出现数据错乱。

那该怎么做呢?

加锁,加锁可以保证某一段代码只能被一个线程执行,防止被争抢。

代码

func Calc() {
    defer wg.Done()
    mutex.Lock()
    NUM = NUM - 1
    mutex.Unlock()
}

第3行加锁,第5行解锁

执行结果

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

这次真的是0的,不管执行几次。

但是会发现一个问题,如果采用这种方式,需要常常注意竞争问题。

所以不是太推荐,需要考虑的比较多,并且各种加锁会消耗性能。

channel语法

channel格式

var 变量名 chan 类型
例如
var x1 chan int //x1管道里面只能存int类型数据
var x2 chan string //x2管道里面只能存字符串类型数据

注意

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

定义管道时,chan int是一个整体,别搞错了各位。

创建channel

创建channel,只能通过make创建。

格式

var 变量名 = make(chan 类型,[管道大小])
示例
var chan1 = make(chan int,10)//管道可以放10个int元素
var chan2 = make(chan string,5)//管道可以放5个string元素

channel操作

创建一个管道。

ch = make(chan int,10)

channel是一个管道,就像一个管子。

所以可以像管子里面塞东西,并且能取东西关闭管道就是这个管道不能用了,里面的值取完就打样了

像管子塞东西(发送)ch <- 666

从管子取东西(接收)var x = <- ch

튜브를 닫으세요close(ch).

참고: 채널은 이와 같은 선입선출 구조입니다.

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

참고:

  • 채널이 꽉 차면 다시 차단하면 차단됩니다.

  • 채널이 닫히면 더 이상 값을 차단할 수 없습니다. 그렇지 않으면 당황하게 됩니다.

  • 채널이 닫혀도 파이프의 값을 완전히 얻을 때까지 값을 얻을 수 있습니다. 값을 얻은 후에는 해당 유형의 0 값을 얻습니다.

  • 파이프라인을 반복적으로 닫을 수 없습니다.


Unbuffered Pipe

Unbuffered는 파이프에 길이가 없다는 것입니다.

就像快读员没有快递柜,需要直接将快递给客户,如果没人要就撂摊子。

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

示例代码

package main


import (
    "fmt"
)


//模拟张三
func 张三(x chan string) {
    var a = <-x
    fmt.Println(a)
}


func main() {
    //通道没有长度,就是无缓冲通道
    var x = make(chan string)
    go 张三(x)
    x <- "张三的快递"
    fmt.Println("张三快递交付成功")
}

第16行写入一个值,同理,张三就要等着去接,如果没人接,那就完了。

假设注释第9行代码。

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

直接报错,all goroutines are asleep - deadlock!,这句话的意思是所有的协程都睡着了,死锁

无缓冲说明通道长度为0发送一个值会阻塞住

这就相当于快递员直接找张三,但是张三没了,但是快递员还得一直等着,等等等,然后挂了,终究还是没送出去。

有缓冲管道

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

这个就简单啦,多了一个快递柜,快递员直接将快递仍快递柜就行了。

示例代码

package main


import (
    "fmt"
    "sync"
)


var wg sync.WaitGroup


//快递员,快递员放10个快递
func 快递员(kuaidigui chan string) {
    defer wg.Done()
    for i := 0; i < 10; i++ {
        fmt.Println("快递员放入了第",i,"快递")
        kuaidigui <- fmt.Sprintf("第%d个快递", i)
}
    //放完快递就关闭了通道
    close(kuaidigui)
}


//张三,拿走3个快递
func 张三(kuaidigui chan string) {
    defer wg.Done()
    for i := 0; i < 3; i++ {
        fmt.Println("张三拿走" + <-kuaidigui)
}
}
//李四拿走7个快递
func 李四(kuaidigui chan string) {
    defer wg.Done()
    for i := 0; i < 7; i++ {
        fmt.Println("李四拿走" + <-kuaidigui)
}
}
func main() {
    //快递柜,10个大小
    var 快递柜 = make(chan string, 10)
    wg.Add(3)
    go 快递员(快递柜)
    go 张三(快递柜)
    go 李四(快递柜)
    wg.Wait()
}

执行结果

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

遍历channel两种方式

代码

func main() {
    //快递柜,10个大小
    var ch = make(chan int, 10)
    //向管道中发送值
    for i := 0; i < 10; i++ {
        ch <- i
}
    //方式一取值
    //for {
    //i, ok := <-ch
    ////取完值ok就是false
    //if !ok {
    //      //结束循环
    //      break
    //}
    //fmt.Println(i)
    //}
    //方式二取值
    for i:=range ch{
        fmt.Println(i)
}
}

执行结果

Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)

报错是因为我在main中完成了发送值和取值两个操作,所以会出现上述问题,但是结果是没有错的。

단방향 채널

우리는 채널 값을 보낼 수 있고 ​ 값을 취할 수 있다는 것을 알고 있습니다. 일부 시나리오에서는 안전을 위해 이유는 이론적으로 값만 받을 수 있고 후자는 값만 보낼 수 있기 때문입니다. 단방향 채널은 일반적으로

기능 매개변수에만 반영됩니다.

    형식 매개변수 chan<-chan 유형
  • 쓰기 전용입니다. 形参 chan<- chan类型只写。

  • 形参 <-chan chan类型

    형식 매개변수 <-chanchan 유형

  • 읽기 전용입니다.

🎜위 택배번호를 수정하세요. 🎜🎜
package main


import (
    "fmt"
    "sync"
)


var wg sync.WaitGroup


//快递员,快递员放10个快递,只写 chan<- string
func 快递员(kuaidigui chan<- string) {
    defer wg.Done()
    for i := 0; i < 10; i++ {
        fmt.Println("快递员放入了第", i, "快递")
        kuaidigui <- fmt.Sprintf("第%d个快递", i)
}
    //放完快递就关闭了通道
    close(kuaidigui)
}


//张三,拿走3个快递,只读<-chan string
func 张三(kuaidigui <-chan string) {
    defer wg.Done()
    for i := 0; i < 3; i++ {
        fmt.Println("张三拿走" + <-kuaidigui)
}
}


//李四拿走7个快递
func 李四(kuaidigui <-chan string) {
    defer wg.Done()
    for i := 0; i < 7; i++ {
        fmt.Println("李四拿走" + <-kuaidigui)
}
}
func main() {
    //快递柜,10个大小
    var 快递柜 = make(chan string, 10)
    wg.Add(3)
    go 快递员(快递柜)
    go 张三(快递柜)
    go 李四(快递柜)
    wg.Wait()
}

总结

上述讲述了Go语言并发如何和channel配合使用,毕竟我们一般的任务都不是单独运行的,都是互相配合的。

我们讲述了如何创建channel如何使用channel有缓冲管道和无缓冲管道区别,并且拒了一个快递员例子来展示协程和channel如何配合,最后用单向通道又加固了一下代码。

我的代码中使用了中文命名变量名是为了好看,实际开发中千万不要这样!!!

위 내용은 Go 언어 동시성의 기본을 이해하는 데 도움이 되는 기사(채널)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 Go语言进阶学习에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제