zoukankan      html  css  js  c++  java
  • go channel 概述

    精髓

    将资源读进内存-->共享内存,一个个进程/线程进行处理,这是常见模式。go channel 是一种直接在进程/线程之间传递资源的方式,即以通信来共享内存。这便是go的精髓。

    扩展-一些名词了解

    Linux、IPC(进程间通信)、进程(六个状态)、线程、同步、异步、信号、管道、socket、消息队列、字节流、结构化消息、通信、信号量、共享内存、内核空间、用户空间、PID、PPID、fork、COW

    将进程细化为多个状态后,系统CPU就可以更加灵活地调度,进程下还有更细的线程,有N多状态,

    GO调度器实现一套机制,统一调度与分配这些CPU、进程、线程等资源,分为M(内核)、P(go上下文)、G(goroutine)三层

    以上概念不学linux原理的话,听个名词就好,GO中重点学习两个点

    1. 第三层G就是我们经常使用的goroutine协程,掌握goroutine的使用

    2. GO支持的IPC方法有 信号、管道、socket,掌握这三种的实现方式

    1. 掌握goroutine的使用

    ---------------------------------------------------------------------------------------------------------

    定义

    chan T      双向

    chan<-T   只发送

    <- chan T 只接收

    通道类型,也是引用类型,零值为nil

    特性

    同一时刻,仅有一个goroutine可以向该通道发送元素值,同时也仅有一个goroutine可以从该通道接收元素值,即通道是串行的。

    通道中的元素值,严格按发送到该通道的先后顺序排列,最先发送到的元素值,一定最先被接收。等效于先进先出的消息队列。

    通道中的元素值,具有原子性,不可被分割;每个元素值只能被一个goroutine接收,被接收后,立刻从通道中清除。

    goroutine的执行与主程序是并行的,主程序结束时,还没有执行完毕的goroutine会被强制中止。

    通道的传值是复制,不是引用。

    通道初始化

    通道未初始化时其值为nil,可以从nil中尝试接收元素,但会被永远阻塞

    make(chan int)

    初始化一个可以接收、发送int值类型的通道,无缓冲

    make(chan int, 10)

    初始化一个可以接收、发送int值类型的通道,可缓冲10个int值。第11个int值向通道中发送时会被阻塞。

    被缓冲的元素值,会严格按发送的顺序接收。

    从通道中接收元素

    c := make(chan int, 10) 

    n := <- c 

    如果通道 c 被关闭,那么n的值为该元素类型的零值,本例int类型的零值为0;如果是在接收的过程中被关闭了,n的值同样为0。

    package main
    
    import "fmt"
    
    func main() {
        c:= make(chan int,10)
        close(c)
        n:=<-c
        fmt.Println(n)
    }

    n,ok := <- c

    这种写法与上面的唯一区别在于,当通道关闭时,ok的值为false

    package main
    
    import "fmt"
    
    func main() {
        c:= make(chan int,10)
        close(c)
        n,ok:=<-c
        fmt.Println(n,ok) // 0 false
    }

     让后台协程有序执行

    本例很好地诠释了前面的概念描述。tools包是个人自定义包,尝试运行时,可自行修改一下代码,个人为了方便就不再更改本地环境代码了。

    package main
    
    import (
        "sync"
        "time"
        "tools"
    )
    
    func main() {
        startTask()
    }
    
    
    func startTask()  {
    
        cmdList := make([]string,6)
        cmdList[0] = "mkdir -p /opt/test/dir1"
        cmdList[1] = "mkdir  /opt/test/dir1/dir2"
        cmdList[2] = "mkdir  /opt/test/dir1/dir2/dir3"
        cmdList[3] = "mkdir  /opt/test/dir1/dir2/dir3/dir4"
        cmdList[4] = "mkdir  /opt/test/dir1/dir2/dir3/dir4/dir5"
        cmdList[5] = "mkdir  /opt/test/dir1/dir2/dir3/dir4/dir5/dir6"
    
        var cmdLength = len(cmdList)
        var cmdChan = make(chan string,cmdLength)
    
        var onOff = make(chan int,cmdLength)
    
    
        var wg sync.WaitGroup
        wg.Add(cmdLength)
        for i,cmd := range cmdList {
            cmdChan <- cmd
            go task(&cmdChan,&onOff,&wg,i,cmdLength)
        }
        //启动第一个任务,第一个任务结束时会启动第二个任务,依次类推
        onOff <- 0
        wg.Wait()
    }
    
    func task(cmdChan *chan string,onOff *chan int,wg *sync.WaitGroup, seq,maxLength int)  {
    
        for  {
            if i,ok := <- *onOff; ok {
                if i == seq {
                    //跳出循环等待,开始当前顺序命令执行
                    break
                }else {
                    //将取出的命令执行序号放回通道,然后暂停一段时间该后台协程
                    *onOff <- i
                    //让不该执行的后台协程等待,就会让该执行的后台线程更容易获取执行机会,因为它不用等
                    time.Sleep(10*time.Microsecond)
    
                }
            }
        }
    
        //从缓冲中取出的元素顺序,严格遵从放入时的顺序
        //所以取出的顺序一定会按命令列表的下标从0排列到列表结束
        if cmd,ok := <- *cmdChan; ok{
            tools.ExecShell(cmd)
        }
        wg.Done()
    
        seq++
        if seq < maxLength{
            *onOff <- seq
        }
    
    }

    linux mkdir有个特性,其父目录若不存在,则无法创建子目录;若命令不按列表顺序执行,那么最后的目录肯定不会创建成功,

    若成功创建所有目录,则表明命令是按顺序执行的。

    若去掉onOff顺序控制,本例就是一个后台并发执行的例子。

     关闭通道

     通常在发送端关闭通道

    不可重复关闭通道,关闭一个已经关闭的或未初始化的通道会引发异常

    通道关闭后,其中未接收的数据仍可被接收

    接收端应先判断通道是否关闭再从中取值,否则若通道关闭可能取出的值是通道类型的零值

    package main
    
    import "fmt"
    
    func main() {
        dataChan := make(chan int,3)
        startChan := make(chan string,1)
        overChan := make(chan string,2)
    
        go func() {
            <- startChan
            fmt.Println("start receive data ")
            for{
                if elem,ok:= <- dataChan; ok{
                    fmt.Printf("%v
    ",elem)
                }else {
                    break
                }
            }
            fmt.Println("reciver over")
            overChan <- "rec over"
        }()
    
        go func() {
            for i:=0;i<3;i++ {
                dataChan <- i
                fmt.Sprintf("send data:%v
    ",i)
            }
    
            fmt.Println("send data over")
            //在接收之前关闭通道
            close(dataChan)
            fmt.Println("dataChan  closed")
    
            //开始接收
            startChan <- "begin"
    
            overChan <- "send data over"
        }()
    
        <- overChan
        <- overChan
        fmt.Println("main over")
        
    }

     长度与容量

    长度是通道中元素的个数,非固定值

    容量是通道中可能缓存的元素个数

    package main
    
    import "fmt"
    
    func main() {
    
        c1 := make(chan int,5)
        fmt.Printf("c1 长度:%v, 容量:%v
    ",len(c1),cap(c1))
    
        c2 := make(chan int)
        fmt.Printf("c2 长度:%v, 容量:%v
    ",len(c2),cap(c2))
    }
    c1 长度:0, 容量:5
    c2 长度:0, 容量:0

     单向通道

    chan T      双向    用于channel定义

    chan<-T   只发送  用于接口、函数参数定义

    <- chan T 只接收  用于接口、函数参数定义

    双向通道可以转换为发送/接收通道,反之不可以。

     for 与 channel

     可以从未初始化的通道(nil)中取值,但会被阻塞

    当通道关闭时,会将通道中的元素全部取出后,语句结束

    必须是一个双向通道或接收通道,不是只是发送通道。

        var c3 chan int
        for elem := range c3 {
            fmt.Println(elem)
        }

     Linux进程

    linux进程通过调用系统函数fork创建,首个系统进程为/usr/bin/sbin,其他进程都是该进程的子进程,呈现树状结构。

    进程创建方式:复制父进程的数据段、堆、栈等数据,共享父进程的代码段,复制后子进程、父进程相互间是独立的。系统内核通过写时复制(copy on write,COW)来提高创建进程的效率。

    Linux进程详细的结构如下,大致分为四部分 代码区(Text segment)-只读静态区,Data段,Heap段,Stack段。

     进程状态图

     注意这里的僵尸状态,是不是僵尸状态是子进程反馈给父进程的,如果子进程自己发生意外,无法给父进程反馈但还一进存在着,那么子进程的状态可能会成为僵尸状态

    SIGCHILD只是在子进程退出的时候发送给父进程的一个信号值,这是一种异步通知父进程的方式.父进程可以捕获,忽略这个信号,默认动作是忽略此信号.
    常用的使用方式是,当SIGCHILD信号发生时候,主进程在SIGCHILD的信号处理函数中调用waitpid or wait来回收子进程的结束状态。但需要明白的是:waitpid or wait不是依靠SIGCHLD信号是否到达来判断子进程是否结束,(可能是通过轮巡检测子进程状态来判断的,需要看具体代码实现才能确定),即wait/waitpid并不依赖于SIGCHILD信号.常常在SIGCHILD的信号处理函数中调用    wait/waitpid回收子进程状态是为了避免wait/waitpid不必要的轮巡,是属于一种节约资源的方式,但这并不是必须的。

    未完.....

  • 相关阅读:
    亲测——pycharm下运行第一个scrapy项目 ©seven_clear
    [转]pycharm的一些快捷键
    一个豆瓣API的使用——拒绝思维定式
    小试牛刀--利用豆瓣API爬取豆瓣电影top250
    Python GUI编程--Tkinter
    多线程详解
    K8S+GitLab-自动化分布式部署ASP.NET Core(一) 部署环境
    初学者浅度剖析eShopOnContainers 里面用到的MediatR .
    通过Task异步加快对数组的运算
    为什么是容器?
  • 原文地址:https://www.cnblogs.com/perfei/p/12593566.html
Copyright © 2011-2022 走看看