关于golang:Golang协程和channel使用

2次阅读

共计 3135 个字符,预计需要花费 8 分钟才能阅读完成。

简介

协程是 golang 的一大特色和卖点. 协程 (goroutine) 是轻量级的执行线程, 应用go 关键字到函数或者 lamba 表达式能够疾速启动协程. 协程函数的返回值会被摈弃. 线程的调度由操作系统来治理,是抢占式调度。而协程不同,协程须要互相配合,被动交出执行权。

配置

GOMAXPROCS 设置逻辑 CPU 数量, 个别状况下应用 CPU 外围数量值. 应用足够的线程来进步 golang 的并行执行效率. 如果你的业务是 IO 密集型则能够设置数倍与 CPU 外围数的值来失去更好的性能. 如果 Go 程序在容器中执行则须要依据状况缩小该值得, 因为容器中无奈应用到宿主机的全副外围. 设置更小的值能够防止线程切换的开销.

应用 channel 进行协程通信

定义通道

ch1 := make(chan string) // 定义了一个无缓冲的 string 通道
ch2 := make(chan string , 4) // 定义了一个 4 个元素的 string 通道

通道操作符

ch1 <- "Chengdu" // 向通道写入数据
itemOfCh1 := <- ch1 // 从 ch1 通道读取一条数据
<- ch1 // 读取通道的下一个值
var in_only chan<- int // 只能接管通道
var out_only <-chan int // 只读取的通道
close(ch) // 敞开通道

通道阻塞

默认状况通道是同步无缓冲的, 在接受方未筹备好之前发送方是阻塞的. 通道中没有数据则接管方也是阻塞的.

package main

import (
    "fmt"
    "time"
)

func f1(in chan int) {
    data := <-in
    fmt.Println(data)
}

func main() {out := make(chan int)
    out <- 2
    fmt.Println(v)
    go f1(out)
    time.Sleep(100 * time.Millisecond)
}

以上程序会 panic 退出, 因为 out 写入数据并没有接受者, 因而 main 主协程被阻塞了. 前面的代码永远不会被执行, 因而通道永远不会有数据, 产生了死锁.批改 out:=make(chan int , 1) 让通道有一个缓冲则不会死锁. 或者在写入前启动读取的协程. 或者在另外一个协程来读取都能够解决这个问题.

应用信号量

能够通过信号量来让主协程期待子协程的实现退出执行.

package main

import (
    "fmt"
    "time"
)

func f1(in chan int, done chan int) {
    data := <-in
    fmt.Println(data)
    time.Sleep(10e9)
    done <- 1
}

func main() {out := make(chan int)
    done := make(chan int)
    go f1(out, done)
    out <- 2
    <-done
}

输入 2 之后 10 秒后程序才会退出, 咱们就不须要应用 sleep 来让主过程执行.

敞开通道

显式的敞开通道, 敞开通道示意发送者不会有新的数据发送给接受者了. 只有发送者须要敞开通道.

ch := make(chan int)
defer close(ch)

data,ok := <-ch // 接管到数据则 ok 为 true, 应用 ok 能够检测通道是否敞开或者阻塞

上面这种状况, 读取通道在主过程不会报死锁谬误, 因为查看到通道敞开后就不进行通道读取跳出循环, 因而不会再继读没有写入的通道. 所以没有死锁.

package main

import "fmt"

func makeStream(n int) chan bool {ch := make(chan bool, n)
    go func() {
        for i := 0; i < n; i++ {ch <- true}
        close(ch)
    }()
    return ch
}

func main() {stream := makeStream(5)

    for {
        v, ok := <-stream
        if !ok {break}
        fmt.Println(v)
    }
}

应用 select 切换协程

从不同并发协程获取值能够用 select 关键字来进行轮训. 通常和 for 循环一起应用

  • 如果都阻塞了期待其中一个能够解决
  • 如果多个能够解决随机抉择一个. 外层有循环则下次再解决残余的
  • 如果没有通道能够解决而有 default 则执行 default. 否则始终阻塞
  • 如果没有 case, 这 select 会始终阻塞
  • 能够应用 break 跳出 select
package main

import (
    "fmt"
    "time"
)

func main() {ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        for i := 0; i < 10; i++ {ch1 <- fmt.Sprintf("A%d", i)
        }
    }()

    go func() {
        for i := 0; i < 10; i++ {ch2 <- fmt.Sprintf("B%d", i)
        }

    }()

    go func() {
        for {
            select {
            case v := <-ch1:
                fmt.Println(v)
            case v := <-ch2:
                fmt.Println(v)
            }
        }
    }()

    time.Sleep(1e9)
}

能够应用这种模式做为服务端来循环解决客户申请

计时器(Ticker)

type Ticker struct {
    C <-chan Time // The channel on which the ticks are delivered.
    r runtimeTimer
}

定时器的 C 变量会依据你创立的定时器工夫, 在给定工夫外向该通道写入工夫

package main

import (
    "fmt"
    "time"
)

func main() {t := time.NewTicker(time.Second)

    go func() {
        for {
            v := <-t.C
            fmt.Println(v)
        }
    }()

    time.Sleep(10e9) // <-time.After(10e9) 应用通道来设置超时
}

应用 time.Tick(duration)能够间接获取通道, 相似time.NewTicker(1e9).C

time.After(duration)只发送一次工夫. 能够应用这个通道来解决超时

协程的复原

协程在遭逢 panic 时平安退出, 而不影响其余协程

package main

import (
    "log"
    "time"
)

func doWork() {time.Sleep(4e9)
    panic("fk")
}

func main() {go func() {
        for {log.Printf("another worker")
            time.Sleep(1e9)
        }
    }()

    go func() {defer func() {if err := recover(); err != nil {log.Printf("出问题了 %s", err)
            }
        }()

        doWork()}()

    time.Sleep(10e9)
}

应用锁还是通道

在一种场景下, 有多个工作, 一个 worker 解决一项工作. 这种场景很适宜应用通道和协程来解决问题

package main

type Task struct{}
type Result struct{}

func process(Task *Task) Result {return Result{}
}

func main() {tasks, results := make(chan Task), make(chan Result)

    workCount := 10

    // 创立工作
    go func() {
        for i := 0; i < workCount; i++ {tasks <- Task{}
        }
    }()

    // 启动 worker
    for i := 0; i < workCount; i++ {go func() {
            for {
                t := <-tasks
                result := process(&t) // 解决数据
                results <- result     // 写入构造
            }
        }()}

    // 生产后果

}
  • 应用锁的情景:

    • 访问共享数据结构中的缓存信息
    • 保留应用程序上下文和状态信息数据
  • 应用通道的情景:

    • 与异步操作的后果进行交互
    • 散发工作
    • 传递数据所有权
正文完
 0