什么是协程?

  1. 协程相似于线程,然而比线程更加轻量。一个程序启动会占用一个过程 而一个过程能够领有多个线程 ,一个线程能够领有多个协程。
  2. 一个过程至多蕴含一个主线程,一个主线程能够有更多的子线程。 线程有两种调度策略,一是:分时调度,二是:抢占式调度。
  3. 对于操作系统来说 线程是最小的执行单元 过程是最小的资源管理单位 线程个别有五种状态:初始化、可运行、运行中、阻塞 、销毁。
  4. 协程是用户态执行,不禁操作系统内核治理 是齐全由程序本人调度和管制的 。
  5. 协程的创立、切换、挂起、销毁全副为内存操作。
  6. 协程属于线程,协程是在线程外面执行的。协程调度策略:合作式调度。

#### go的goroutine

go是一个种对并发十分敌对的语言。它提供了两大机制的简略语法:协程(goroutine)和管道(channel)。

goroutine是轻量级的线程 go在语言层面就反对原生协程
go的协程绝对于线程开销更小 大略在2kb 依据程序开销需要增大或者放大 线程必须指定堆栈的大小 大小是固定的
goroutine 是通过 GPM 调度模型实现的。GPM 调度模型

简略应用协程原生反对

package mainimport ( "fmt" "time")func main()  { fmt.Println("测试") // 这里开始异步go func() {    time.Sleep(time.Microsecond*10)    fmt.Println("测试3") }() fmt.Println("测试3") //提早主程序退出time.Sleep(time.Microsecond*100)}

Go属于多线程版协程,能够利用多核CPU,同一时间能够有多个协程在调度,会存在并发问题。

上面这段代码,执行后果如何。按失常应该是打印1-20
package mainimport (    "fmt"    "time")var count =0func main()  {    for i:=0;i<=20;i++ {        go func() {            count ++            fmt.Println(count)        }()    }   time.Sleep(time.Microsecond*100)}
$ go run main.go //第一次执行135214181910111213615161748209721$ go run main.go //第二次执行131825781910111213141516174920216

每次执行后果是不一样的。在做写入操作的时候 同时多个协程写入 导致数据乌七八糟的打印
从变量中读取变量是惟一平安的并发解决变量的形式。 你能够有想要多少就多少的读取者, 然而写操作必须要得同步。 有太多的办法能够做到这个了,包含应用一些依赖于非凡的 CPU 指令集的真原子操作。然而,罕用的操作还是应用互斥量。

写入数据时给协程加锁

package mainimport (   "fmt"   "sync"   "time")var (   lock sync.Mutex   count =0)func main()  {   for i:=0;i<=20;i++ {       go func() {           lock.Lock()           defer lock.Unlock()           count ++           fmt.Println(count)       }()   }   time.Sleep(time.Microsecond*100)}

咱们在给count变量自增时加锁,保障同一时间只有一个协程在写入 后果和咱们心愿的后果一至。

$ go run main.go123456789101112131415161718192021
看似咱们解决了并发问题,但也违反并发编程的初心。而且容易造成死锁问题。应用单个锁时,这没有问题,然而如果你在代码中应用两个或者更多的锁,很容易呈现一种危险的状况,当协程 A 领有锁 lockA ,想去拜访锁 lockB ,同时协程 B 领有锁 lockB 并须要拜访锁 lockA 。

通道

通道是多协程调度资源共享的一个弱小机制 是协程之间传递数据的共享管道。一个协程能够通过管道向另外一个协程传递数据 所以在任意一个工夫点 只有一个协程能够拜访数据

创立一个管道

c := make(chan int)

这个通道的类型是 chan int。因而,要将通道传递给函数,咱们的函数签名看起来是这个样子的:

func worker(c chan int) { ... }

管道反对两个操作

//接收数据CHANNEL <- DATA//发送数据VAR := <-CHANNEL

应用 for 进行管道数据接管或者发送

举个例子

package mainimport (  "fmt"  "time"  "math/rand")func main() {  c := make(chan int)  for i := 0; i < 5; i++ {    worker := &Worker{id: i}    go worker.process(c)  }  for {    c <- rand.Int()    time.Sleep(time.Millisecond * 50)  }}type Worker struct {  id int}func (w *Worker) process(c chan int) {  for {    data := <-c    fmt.Printf("worker %d got %d\n", w.id, data)  }}

缓冲通道

无缓冲管道的发送和接管过程是阻塞的,还能够创立一个有缓冲(Buffer)的管道。
只在缓冲已满的状况,才会阻塞向缓冲管道(Bufferer Channel)发送数据。同样,只有在缓冲为空的时候,才会阻塞从缓冲管道接收数据。
通过向make函数再传递一个示意容量的参数(指定缓冲的大小),能够创立缓冲管道。
要让一个管道有缓冲,下面语法中的capacity应该大于0。无缓冲管道的容量默认为0.
ch := make (chan type, capacity)

select

即便有缓冲,在某些时候咱们须要开始删除音讯。咱们不能为了让 worker 轻松而耗尽所有内存。为了实现这个,咱们应用 Go 的 select:

select的作用

Go提供了一个关键字select。通过select能够监听channel下面的数据流动,由select开始一个抉择 抉择条件由case语句形容。
select应用限度,每个case语句里必须是一个IO操作。所以个别select须要配合协程管道来应用
举个例子:

for {  select {  case c <- rand.Int():    // 可选的代码在这里  default:    // 这里能够留空以静默删除数据    fmt.Println("dropped")  }  time.Sleep(time.Millisecond * 50)}

超时

for {  select {  case c <- rand.Int():  //指定工夫后做相干操作 次要做同步工作例如申请接口须要返回数据 超过响应时常则报错避免协程始终阻塞  case <-time.After(time.Millisecond * 100):     fmt.Println("timed out")  default //默认执行 没有default,select会阻塞      }  time.Sleep(time.Millisecond * 50)}