美文网首页GoGo语言实践
Go并发编程(下)

Go并发编程(下)

作者: 帅气的昵称都有人用了 | 来源:发表于2019-04-08 16:54 被阅读3次

    通道

    上一次我们讲到了协程,也就是goroutine,并且我们知道了除了goroutine的方式之外,还有一种被称为消息通道机制的通信方式,也就是今天我们要介绍的channel

    \color{blue}{channel是Go语言在语言级别提供的goroutine间的通信方式,可以使用channel在两个或多个goroutine之间传递消息。}

    channel是进程内的通信方式,因此我们可以传递包括指针在内的各种参数,但如果涉及到跨进程通信时,最好我们要考虑一下使用分布式系统的方法来进行解决,比如使用比较常见的SocketHTTP等通信协议。
    我们在了解channel之前,还需要记住一句话:

    \color{orange}{channel是类型相关的,一个channel只能传递一种类型的值,而且这个类型需要在声明channel时制定}
    我们先来看一个例子:

    package main
    
    import "fmt"
    
    var count int = 0
    
    func Count(ch chan int) {
        ch <- count
        count++
        fmt.Println("Counting...", count)
    }
    
    func main() {
        chs := make([]chan int, 10)
        for i := 0; i < 10; i++ {
            chs[i] = make(chan int)
            go Count(chs[i])
        }
        for _, ch := range chs {
            <- ch
        }
    }
    /** The result is:
    Counting...0
    Counting...1
    Counting...2
    Counting...3
    Counting...4
    Counting...5
    Counting...6
    Counting...7
    Counting...8
    Counting...9
    Counting...10
     */
    

    我们在这个例子当中定义了一个包含10个channel的数组,并且把数组中的每个channel分配个10个不同的goroutine,通过全局变量count实现依次输出1-10的操作,在所有goroutine启动完成后,通过<-ch语句从10个channel中依次读取数据,得到如上结果。
    其实,在对应的channel写入数据之前,这个读取数据的操作是阻塞的,这样一来,就可以使用channel实现类似的功能了。也进而保证了所有goroutine在完成后主函数才能返回。


    基本语法

    接下来我们先了解一些channel的基本语法:
    一般channel的声明形式为:

    var chanName chan ElemType
    

    比如我们声明一个map,元素是bool型的channel:

    var m map[string] chan bool
    

    定义一个channel也是很简单的:

    ch := make(chan int)
    

    在声明之后,就设计到channel的基本操作了,当然最主要的操作就是写入读出了。
    将数据写入至channel的语法为:

    ch <- value
    

    在这里要注意,向channel写入数据通常会导致程序的阻塞,知道有其他gotoutine从这个channel中读取数据。读取语法为:

    value := <-ch
    

    select 关键字

    Go语言在语言级别是直接支持select的关键字的,用于处理异步I/O问题。
    其实selectswitch是非常类似的,但select具有更多的限制,其中最大的一个限制就是每个case语句中必须是一个I/O操作,大致的结构如下:

    select {
    case <- chan1:  //如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:  //如果成功向chan2写入数据,则进行该case处理语句
    default:  //如果上面都没有成功,就执行default中的内容    
    } 
    

    从上述的内容可以看出,select并不像switch,后面是不带判断条件的,而是直接去查看case语句。


    缓冲机制

    前面的channel都是不带缓冲的,但是在面对大量的数据的时候,单个传递的channel(不带缓冲)就有些不太合适了,接下来介绍如何给channel带上缓冲,从而达到消息队列的效果。
    创建带缓冲的channel

    c := make(chan int, 1024)
    

    即在调用make()时将缓冲区大小作为第二个参数传入即可。
    这样一来,写入方可以一直往channel里面写,在缓冲区被填完之前都不会阻塞。


    超时和计时器

    在前面的介绍与讲述中,并没有提及有关错误处理的任何问题,而这个问题显然是不能被忽略的,和许多地方一样,最需要考虑的一个问题就是超时的问题。

    \color{orange}{如果不正确处理可能出现超时问题的情况,很可能会导致整个goroutine锁死}

    Go语言并没有提供直接处理超时的机制,但我们可以使用select机制,通过使用select,可以很方便的解决超时问题。

    timeout := make(chan bool, 1)
    go func() {
        time.Sleep(1e9)
        timeout <- true
        }() 
        select {
        case <- ch: // 从ch中读取到数据
        case <- timeout: // 一直没有从ch中读取到数据,但从timeout中读取到了数据
    }
    

    channel的传递

    Go语言中channel本身也是一个原生类型,与map之类的类型地位一样,因此channel本身在定义后也可以通过channel来传递。我们可以使用这一特性来实现Linux/UNIX中非常常见的管道pipe特性。

    /** 首先先限定基本的数据结构 */
    type PipeData struct {
        value int
        handler func(int) int
        next chan int
    }
    
    /** 一个十分简单的函数 */
    func handle(queue chan *PipeData) {
        for data := range queue {
            data.next <- data.handler(data.value)
        }
    }
    

    单向channel

    顾名思义,单向channel是只能用于发送或者接受数据的。而且定义方法也非常简单:

    var ch1 chan int // ch1是一个非常正常的channel
    var ch2 chan<- float64 // ch2是单向的channel,只用于写float64数据
    var ch3 <-chan int // ch3是单向channel,只用于读取int数据
    /** 类型的强制转换对于channel来说也是可以的*/
    ch4 := make(chan int)
    ch5 := <-chan int(ch4)
    ch6 := chan<- int(ch4)
    

    我们设计单向channel的主要原因就是要保证所有代码都遵循“最小权限原则”

    func Parse(ch <-chan int) {
        for value := range ch {
            fmt.Println("The value after parse is", value)
        }
    }
    

    在这里例子当中,如果不做其他任何考虑的话,整个程序是只需要进行读操作的,因此我们将channel定义为了单向channel以最大化节约资源。


    关闭channel

    close(ch)
    

    只需要一个close函数即可实现channel的关闭。
    当然,如果我们希望判断一个channel是否已经被关闭,那么我们可以使用多重返回值的方式:

    x,ok := <-ch
    

    这个用法与map中的案件获取value的过程比较类似,只需要看第二个bool返回值即可,如果返回值是false则表示ch已经被关闭。


    到这里关于Go语言中的并发内容的介绍就基本上结束了,接下来将有什么更加有趣的东西等待着我们呢?我们一同期待~

    相关文章

      网友评论

        本文标题:Go并发编程(下)

        本文链接:https://www.haomeiwen.com/subject/topyiqtx.html