• Go 语言并发笔记


    前言: 本文是学习<<go语言程序设计>> -- 清华大学出版社(王鹏 编著) 的2014年1月第一版 做的一些笔记 , 如有侵权, 请告知笔者, 将在24小时内删除, 转载请注明出处!

    1. Goroutine

      - 定义: 在语言级别上支持的轻量级线程.  

      - Go标准库提供的所有操作系统调用操作(包括同步I/O操作), 都会让出处理机给它. 所以它的切换和管理不依赖于系统的进程和线程

      - 是go语言库的功能 , 而不是操作系统的功能. Goroutine不是用线程实现的. 本质是一段代码, 一个函数入口, 以及在堆上为其分配的一个堆栈. 

      - 省去了频繁创建和销毁线程的开销, 可以创建上百万个Goroutine, 但是它们并不是直接被操作系统调度. 

      - 其他语言一般通过库的方式支持协程, 但是在这种协程中调用一个同步IO操作,比如网络通信, 本地文件读写都会阻塞其他并发执行的协程.

      - 创建方式: 在一个函数前面加上 go 关键字, 这次调用就会在一个新的Goroutine中并发执行, 直到函数返回.( 注: 函数返回值会被丢弃)

    2. Channel

      - 定义 : Go 采用消息机制作为并发单位之间的通信手段. 

      - 每个并发单位是自包含的, 独立的个体, 并且都有自己的变量. 这些变量不能在不同的并发单位之间共享.

      - 类似Pipe, 可以使用Channel在两个或多个Goroutine之间传递消息,  Channel在设计上确保同一时刻只有一个Goroutine能从中接收数据. 从而避免使用互斥锁的问题.

      - Channel的发送和接收都是原语操作, 不会中断, 只会失败.

      - Channel是进程内的通信方式, 进程间通信一般采用分布式的方法, 如: Socket 或者HTTP等.

      - 声明和初始化:   var channelName chan ElementType  ElementType 指定Channel所能传递的元素类型.

    3. 数据的接收和发送

      - 使用Channel在不同的Goroutine中传递数据. 使用通道运算符号:  <- 

      - 发送  channelVar <- value 

      - 接收  value := <- ch 

      - 向Channel写入数据会使  程序(应该是Goroutine吧)阻塞, 知道有其他的Goroutine从这个Channel中读取数据.

      - 如果Channel之前没有写入数据, 那么从Channel中读取数据也会阻塞  程序(应该是Goroutine吧), 直到有写入数据为止.

    package main
    
    import(
        "fmt"
        "math/rand"
    )
    
    func Test(ch chan int){
            // 不明白结果中为什么有的End没有输出来?
        fmt.Println("Begin...",ch)
        ch <- rand.Int()
        fmt.Println("End ...",ch)
    }
    
    func main() {
        chs := make([]chan int,10)
        for i := 0;i<10;i++ {
            chs[i] = make(chan int)
            go Test(chs[i])
        }
        for _,ch := range chs{
            value := <- ch
            fmt.Println(value)
        }
    }
    View Code

    4. Channel的关闭和迭代器

      - 关闭: 使用GO的内置函数 close() :  close(chName) 

      - 关闭一个Channel只后往往还有判断Channel是否被关闭:  value,ok := <- ch // ok 为false表示ch已经关闭. 

      - 使用迭代器读取.

        - 使用range

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        ch := make(chan int)
        go func() {
            for i := 0; i < 5; i++ {
                ch <- i
            }
            close(ch)
        }() // 这里立即调用
        for value := range ch {
            fmt.Println(value)
        }
    }
    View Code

        - 使用  if value, ok := <- ch; ok { ... } // 判断是否已经关闭ch, 从而遍历ch的值 

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        ch := make(chan int)
        go func() {
            for i := 0; i < 5; i++ {
                ch <- i
            }
            close(ch)
        }()
        for {
            if value, ok := <-ch; ok {
                fmt.Println(value)
            } else {
                break
            }
        }
    }
    View Code

      - 如果接收端使用range或者循环接受数据, 那么必须调用close()

    5. 单向Channel

      - 定义: 

        - 只写(只能接收):  var chWrite chan <- ElementType 

        - 只读(只能发送):  var chRead <- chan ElementType 

      - 定义了单向Channel之后要对其初始化, 由于Channel是引用数据类型, 也是一个原生数据类型, 因此不仅支持被传递, 还支持类型转换(由双向转单向).

    ch := make(chan,int)
    chRead := <- chan int(ch)
    chWrite := chan <- int(ch)

      - 实例

    package main
    
    import (
        "fmt"
    )
    
    func Recv(ch <-chan int, lock chan<- bool) {
        for value := range ch {
            fmt.Println(value)
        }
        lock <- true
    }
    
    func Send(ch chan<- int) {
        for i := 0; i < 5; i++ {
            ch <- i
        }
        close(ch)
    }
    
    func main() {
        ch := make(chan int)
        lock := make(chan bool)
        go Recv(ch, lock) // 传参的时候转换为单向Channel
        go Send(ch)
        <-lock // 这是关闭操作吗?
    }
    View Code

    6. 异步Channel

      - 对于传递少量数据的应用来说一般使用同步Channel, 对于传递大量数据而言需要使用异步Channel.

      - 使用Buffer 来实现消息队列. 设定一个Buffer的大小 , 在Buffer未写满时不阻塞发送操作, 在Buffer未读完之前, 不阻塞接受操作. 

      - 创建:  ch := make(chan int, 1024) // 指定缓冲区大小即创建了一个带Buffer的Channel. 

      - 代码

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func Worker(sem chan int, lock chan bool, id int) {
        sem <- 1 // 相当于对同步信号量进行P原语操作.
        fmt.Println(time.Now().Format("04:05"), id)
        time.Sleep(1 * time.Second)
        <-sem // 相当于对同步信号量进行V原语操作
        if id == 9 {
            lock <- true
        }
    }
    
    func main() {
        ch := make(chan int, 2)
        lock := make(chan bool)
        for i := 0; i < 10; i++ {
            go Worker(ch, lock, i)
        }
        <-lock
    }
    View Code

    7. Select机制和超时机制

      - Select用于解决通道通信中多路复用的问题. 因为通道的接收操作往往是阻塞式的, 所以Select经常和超时机制配合.

      - 如果有多个channel需要监听, 就可以使用Select随机选择一个可用的channel进行处理.

      - Select在接收端进行监听, 当然Select也可以用于发送端, 如果有其他Goroutine在运行, 还可以使用空Select{}阻塞main()函数, 避免进程终止.

      - 代码:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        ch1 := make(chan int)
        ch2 := make(chan string)
        lock := make(chan bool)
        go func() {
            for {
                select {
                case value, ok := <-ch1:
                    if !ok {
                        lock <- true
                        break
                    }
                    fmt.Println("ch1 = ", value)
                case value, ok := <-ch2:
                    if !ok {
                        lock <- true
                        break
                    }
                    fmt.Println("ch2 = ", value)
                }
            }
        }()
        ch1 <- 100
        ch2 <- "golang"
        ch2 <- "Go"
        ch1 <- 200
        close(ch1)
        close(ch2)
        <-lock
    }
    View Code

      - Select机制

        - 当所有被监听Channel中都没有数据时, Select会等到其中一个有数据为止.

        - 当多个被监听Channel中都有数据时, Select会随机选择一个case执行.

        - 当所有被监听Channel中都没有数据时, 如果default子句存在, 则将会被执行.

        - 如果想持续监听多个Channel, 需要使用for语句协助(how?)

       -  超时机制

        - Go中并发通信过程中所有的错误处理都是由超时机制来完成的

        - 超时机制是一种解决通信死锁的机制, 通常会设置一个超时参数, 通信双方如果在设定的时间内仍然没有处理完任务, 则处理过程会被立即终止, 并返回相应的超时信息.

        - Go语言没有直接提供超时处理机制, 可以用Select机制解决

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        ch := make(chan int)
        timeout := make(chan bool, 1)
        go func() {
            time.Sleep(1 * time.Second)
            timeout <- true
        }()
        select {
        case <-ch:
        case <-timeout:
            fmt.Println("Timeout")
            break
        }
    }
    View Code

         - Go 语言的time包中还提供了After,Tick 等函数, 可以返回计时器Channel, 利用它们也可以实现超时机制.

  • 相关阅读:
    IO流(读取键盘录入)
    IO 流 自定义字节流的缓冲区-read 和write 的特点
    IO流 字节流的缓冲区
    IO流 拷贝图片
    IO流-字节流File读写操作
    IO流 带行号的缓冲区
    IO流(装饰设计模式)
    IO流-ReadLine方法的原理 自定义BufferedReader
    IO流 Buffered 综合练习
    IO流 BufferedWriter
  • 原文地址:https://www.cnblogs.com/roger9567/p/4850629.html
Copyright © 2020-2023  润新知