• Go 语言基础(七) 之 并发和网络


    1、goroutine
    在这章中将展示 Go 使用 channel 和 goroutine 开发并行程序的能力。
    goroutine 是 Go 并发能力的核心要素。但是,goroutine 到底 是什么?
    叫做 goroutine 是因为已有的短语——线程、协程、进程等等——传 递了不准确的含义。
    goroutine 有简单的模型:它是与其他 goroutine 并行执行的,
    有着相同地址空间的函数。。它是轻量的,仅比分配 栈空间多一点点 。
    而初始时栈是很小的,所以它们也是廉价的,并且随着需要在堆空间上分配(和释放)。
     
    goroutine 是一个普通的函数,只是需要使用保留字 go 作为开头。
    ready("Tea", 2) ← 普通函数调用
    go ready("Tea", 2) ← ready() 作为 goroutine 运行
     
    Go routine 实践
    func ready(w string, sec int) {
    time.Sleep(time.Duration(sec) * time.Second)
    fmt.Println(w, "is ready!")
    }
    func main() {
    go ready("Tea", 2)
    go ready("Coffee", 1)
    fmt.Println("I'm waiting")
    time.Sleep(5 * time.Second)
    }
    输出:
    I'm waiting ← 立刻
    Coffee is ready! ← 1 秒后
    Tea is ready! ← 2 秒后
     
    2、用 channel
    如果不等待 goroutine 的执行(例如,移除 I’m waiting 行),
    程序立刻终止,而任何正在执行的 goroutine 都会停止。
    为了修复这个,需要一些能够同 goroutine 通讯的机制。
    这一机制通过 channels 的形式使用。
    channel 可以与 Unix sehll 中的双向管道做类比:可以通过它发送或者接收值。
    这些值只能是特定的类型:channel 类型。定义一个 channel 时,
    也需要定义发送到 channel 的值的类型。
    注意,必须使用 make 创建 channel:
    ci := make(chan int)
    cs := make(chan string)
    cf := make(chan interface{})
    创建 channel ci 用于发送和接收整数,创建 channel cs 用于字符串,
    以及 channel cf 使用了空接口来满足各种类型。
    向 channel 发送或接收数据,是通过类似的 操作符完成的:<-.
    具体作用则依赖于操作符的位置:
    ci <- 1 ←发送整数1 到channelci
    <-ci ← 从 channel ci 接收整数
    i := <-ci ← 从 channel ci 接收整数,并保存到 i 中
     
    将这些放到实例中去:
     
    // 定义 c 作为 int 型的 channel。就是说:这个 channel 传输整数。
    // 注意这个变量是全局的,这样 goroutine 可以访问它;
    var c chan int
    func ready(w string, sec int) {
    time.Sleep(time.Duration(sec) * time.Second)
    fmt.Println(w, "is ready!")
    c <- 1 // 发送整数1 到 channel c;
    }
    func main() {
    c = make(chan int) // 初始化c
    go ready("Tea",2) // 用保留字go 开始一个 goroutine
    go ready("Coffee", 1)
    fmt.Println("I'm waiting, but not too long")
    <-c // 等待,直到从 channel 上接收一个值。注意,收到的值被丢弃了;
    <-c // 两个 goroutines,接收两个值。
    }
     
    如果不知道有启动了多少个 goroutine 怎么办呢?
    这里有另一个 Go 内建的保留字:select。
    通过 select(和其他东西)可以监听 channel 上输入的数据。
     
    在这个程序中使用 select,并不会让它变得更短,因为运行的 goroutine 太少 了。
    移除两行 <-c,并用下面的内容替换它们:
    使用 select
    L: for {
    select {
    case <-c:
    i++
    if i > 1 {
    break L
    }
    }
    }
    现在将会一直等待下去。
    只有当从 channel c 上收到多个响应时才会退出循环 L。
     
    3、并行运行
    虽然 goroutine 是并发执行的,但是它们并不是并行运行的。
    如果不告诉 Go 额 外的东西,同一时刻只会有一个 goroutine 执行。
    利用 runtime.GOMAXPROCS(n) 可以设置 goroutine 并行执行的数量。
     
    GOMAXPROCS 设置了同时运行的 CPU 的最大数量,并返回之前的设 置。
    如果 n < 1,不会改变当前设置。 当调度得到改进后,这将被移 除。
     
    当在 Go 中用 ch := make(chan bool) 创建 chennel 时,bool 型的 无缓冲 channel 会被创建。
    这对于程序来说意味着什么呢?首先,如果读取(value := <-ch)它 将会被阻塞,直到有数据接收。
    其次,任何发送(ch<-5)将会被阻塞,直到数 据被读出。
    无缓冲 channel 是在多个 goroutine 之间同步很棒的工具。
    不过 Go 也允许指定 channel 的缓冲大小,很简单,就是 channel 可以存储多少 元素。
    ch := make(chan bool, 4),创建了可以存储 4 个元素的 bool 型 channel。
    在这个 channel 中,前 4 个元素可以无阻塞的写入。
    当写入第 5 元素时,代码 将会阻塞,直到其他 goroutine 从 channel 中读取一些元素,腾出空间。
     
     
    4、关闭 channel
    当 channel 被关闭后,读取端需要知道这个事情。下面的代码演示了如何检查
    channel 是否被关系。
    x, ok = <-ch
    当 ok 被赋值为 true 意味着 channel 尚未被关闭,同时 可以读取数据。
    否则 ok 被 赋值为 false。在这个情况下表示 channel 被关闭。
     
     
    十七、通讯
    了解文件、目录、网络通 讯和运行其他程序。
    Go 的 I/O 核心是接口 io.Reader 和 io.Writer。
    在 Go 中,从文件读取(或写入)是非常容易的,即使用os包
     
    1、从文件读取(无缓冲)
    package main
    import "os"
    func main() {
    buf := make([]byte, 1024)
    f,_ := os.Open("/etc/passwd") // 打开文件,os.Open 返回一个实现了 io.Reader 和 io.Writer 的 *os.File;
    defer f.Close() // 确保关闭了f;
    for {
    n,_ := f.Read(buf) // 一次读取1024 字节
    if n==0 {break} // 到达文件末尾
    os.Stdout.Write(buf[:n]) // 将内容写入os.Stdout
    }
    }
     
     
    2、从文件读取(缓冲,用bufio包)
    package main
    import ( "os"; "bufio")
    func main() {
    buf := make([]byte, 1024)
    f,_ := os.Open("/etc/passwd") // 打开文件
    defer f.Close()
    // 转换 f 为有缓冲的 Reader。NewReader 需要一个 io.Reader,
    // 因此或许你认为这会出错。但其实不会。任何有 Read() 函数就实现了这个接口。
    // 同时, 从上例可以看到,*os.File 已经这样做了;
    r := bufio.NewReader(f)
    w := bufio.NewWriter(os.Stdout)
    defer w.Flush()
    for {
    n, _ := r.Read(buf) // 从 Reader 读取,而向 Writer 写入,然后向屏幕输出文件。
    if n == 0 { break }
    w.Write(buf[0:n])
    }
    }
     
    3、io.Reader
    io.Reader 接口对于 Go 语言来说非常重要。
    许多(如果不是全 部的话)函数需要通过 io.Reader 读取一些数据作为输入
     
    4、一行一行读取文件
    f,_ := os.Open("/etc/passwd");
    defer f.Close()
    r := bufio.NewReader(f) ← 使其成为一个 bufio,以便访问 ReadString 方法
    s, ok := r.ReadString(' ') { ← 从输入中读取一行
    // ... | ← s 保存了字符串,通过 string 包就可以解析它|
     
     
    5、命令行参数
    一个 DNS 查询工 具:
    // 定义 bool 标识,-dnssec。变量必须是指针,否则 package 无法设置其值
    dnssec := flag.Bool("dnssec",false,"RequestDNSSECrecords")
    port := flag.String("port", "53", "Set the query port") // 类似的,port 选项;
    flag.Usage = func() { // 简单的重定义 Usage 函数
    fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] [name ...] ", os.Args[0])
    flag.PrintDefaults() // 指定的每个标识,PrintDefaults 将输出帮助信息;
    }
    flag.Parse() // 解析标识,并填充变量。
    当参数被解析之后,就可以使用它们:
    if *dnssec { ← 定义传入参数 dnssec
    // 做点
    }
     
    6、执行命令
    os/exec 包有函数可以执行外部命令,这也是在 Go 中主要的执行命令的方法。
    通过定义一个有着数个方法的 *exec.Cmd 结构来使用。
    执行 ls -l:
    import "os/exec"
    cmd := exec.Command("/bin/ls", "-l")
    err := cmd.Run()
    上面的例子运行了 “ls -l”,但是没有对其返回的数据进行任何处理
     
    通过如下 方法从命令行的标准输出中获得信息:
    import "exec"
    cmd := exec.Command("/bin/ls", "-l")
    buf, err := cmd.Output() ← buf 是一个 []byte
     
     
    7、网络
    所有网络相关的类型和函数可以在 net 包中找到。这其中最重要的函数是 Dial。
    当 Dial 到远程系统,这个函数返回 Conn 接口类型,可以用于发送或接收信息。
    函数 Dial 简洁的抽象了网络层和传输层。
    因此 IPv4 或者 IPv6,TCP 或者 UDP 可 以共用一个接口。
     
    通过 TCP 连接到远程系统(端口 80),然后是 UDP,最后是 TCP 通过 IPv6,
    大致 是这样:
    conn, e := Dial("tcp", "192.0.32.10:80")
    conn, e := Dial("udp", "192.0.32.10:80")
    conn, e := Dial("tcp", "[2620:0:2d0:200::10]:80") ← 方括号是强制的
    如果没有错误(由 e 返回),就可以使用 conn 从套接字中读写。
    在包 net 中的原 始定义是:
    // Read reads data from the connection.
    Read(b []byte)(n int, err error)
    这使得 conn 成为了 io.Reader。
    // Write writes data to the connection.
    Write(b []byte)(n int, err error)
    这同样使得 conn 成为了 io.Writer,
    事实上 conn 是 io.ReadWriter。
    但是这些都是隐含的低层
     
    通常总是应该使用更高层次的包:http
    一个简单的 http Get 例子
    package main
    import("io/ioutil";"net/http";"fmt") // 需要的导入
    func main() {
    r,err := http.Get("http://www.google.com/robots.txt") // 使用http.Get获取html
    if err != nil { fmt.Printf("%s ",err.String()); return } // 错误处理
    b, err := ioutil.ReadAll(r.Body) // 将整个内容读入b
    r.Body.Close()
    if err == nil { fmt.Printf("%s",string(b)) } // 如果一切ok,打印内容
    }
  • 相关阅读:
    js 一维数组转二维数组
    mongoose 系列设置
    手写系列
    设置未登录的导航守卫
    vue 添加设置别名文件
    移动端视口标签
    小程序跳转页面怎么携带数据
    data数据复杂时怎么setData
    小程序注意的点 text标签中一定 不要换行
    小程序用户登录
  • 原文地址:https://www.cnblogs.com/wjq310/p/6545515.html
Copyright © 2020-2023  润新知