简介

协程是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 mainimport (    "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 mainimport (    "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 mainimport "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 mainimport (    "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 mainimport (    "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 mainimport (    "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 maintype 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     //写入构造            }        }()    }    //生产后果}
  • 应用锁的情景:

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

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