1、协程介绍
        进程和线程都是由操作系统内核进行调度,有 CPU 时间片的概念,进行抢占式调度。
        协程是用户级的线程,对内核是透明的,系统并不知道协程的存在,并且协程是非抢占式调度,无法实现公平的任务调用,通常只进行协作式调度,需要协程自己主动把控制权转让出去之后,其他协程才能被执行到。
在任务调度上,协程弱于线程;

在资源消耗上,协程则是极低的,一个线程的内存在 MB 级别,而协程只需要 KB 级别。

2、协程的的基本原理
        协程是基于线程的,二者的原理一样,线程是在操作系统内核层面实现的,协程是在应用层实现了线程

        Go Scheduler会把goroutine调度到逻辑处理器上运行,逻辑处理器会一对一的绑定到操作系统的线程。当goroutine可以运行时,会被放入一个逻辑处理器的待执行队列中;当goroutine遇到长时间执行或执行了一个阻塞的系统调用时(如打开文件),Go Scheduler会将这个逻辑处理器与线程分离,并将另一个线程绑定到这个逻辑处理器,之后从待执行队列中选择下一个goroutine来运行,原来的goroutine保存到待执行队列等待调用(逻辑处理器是不动的)。

        每一个goroutine是一个独立的执行单元,相较于每个OS线程固定分配2M内存的模式,goroutine的栈采取了动态扩容方式, 初始时仅为2KB,随着任务执行按需增长,最大可达1GB,且由golang的调度器 Go Scheduler 来调度。此外,GC还会周期性地将不再使用的内存回收,收缩栈空间。

        Go Scheduler调度器能管理所有被创建的 goroutine 并为其分配执行时间。Go Scheduler能将语言运行时的逻辑处理器与操作系统的线程一一绑定,并会全面的控制哪个 goroutine 要在哪个逻辑处理器上运行。

3、Goroutine同步机制

Goroutine提供了一种传统的同步机制——对共享资源加锁。
(1)原子函数能够以很底层的加锁机制来同步访问整型变量和指针。
atomic.AddInt64(&counter, 1),强制同一时刻只能有一个 goroutine 运行并完成这个加法操作,还有LoadInt、StoreInt等。
(2)互斥锁用于在代码上创建一个临界区,保证同一时间只有一个 goroutine 可以
执行这个临界区里的代码。

var mutex sync.Mutex   
mutex.Lock(){
xxx
}
mutex.Unlock()

(3)通道

使用原子函数和互斥锁能够保证对共享资源的安全访问以及消除竞争状态;
使用通道,通过发送和接收需要共享的资源,在goroutine 之间进行同步。
无缓冲的通道(unbuffered channel):指在接收前没有能力保存任何值的通道。这种类型的通
道要求发送goroutine 和接收goroutine 同时准备好,才能完成发送和接收操作,如果两个goroutine
没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待。
有缓冲的通道(buffered channel):是一种在被接收前能存储一个或者多个值的通道。不强制要求goroutine 之间必须同时完成发送和接收。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞;只有在通道中没有要接收的值时,接收动作才会阻塞。

4、多线程-〉异步编程-〉协程
        线程是操作系统的内核对象,多线程编程时,如果线程数过多,就会导致频繁的上下文切换,这些 cpu 时间是一个额外的耗费。

        于是操作系统提供了基于事件模式的异步编程模型,用少量的线程来服务大量的网络连接和I/O操作,但是采用异步和基于事件的编程模型,复杂化了程序代码的编写,非常容易出错,再加上线程穿插,也提高排查错误的难度。

        协程是在应用层模拟的线程,避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂度。
5、协程为什么大热?
        主要用于网络编程,其独有的特点。高并发(每秒钟上千数万的单机访问量),程序生命期短(毫秒,秒级) 高IO,低计算(连接数据库,请求API)。
6、实际应用
        在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行,当被调用的函数返回时,这个goroutine也自动结束。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。