goroutine基本概念

  • 进程和线程的关系
    • 进程就是程序在操作系统中的一次执行过程,是系统分配资源和调度的基本单位。
    • 线程是进程执行的实例,是程序执行的最小单位,比进程更小能独立运行的基本单位
    • 一个进程可以创建或者销毁多个线程,同一个进程可以有多个线程并发执行
    • 一个程序至少有一个进程,一个进程只少有一个线程
  • 并发和并行
    • 多线程程序在单核CPU上运行,叫做并发
      • 其实是在分时执行,同一时间只有一个线程在执行
    • 多线程程序在多核CPU上运行,叫做并行
      • 同一时间点,有多个线程在执行
  • Go的协程和主线程
    • Go的主线程:一个线程上可以起多个协程,可以理解为,协程goroutine是轻量级的线程
    • 主线程是是一个物理线程,直接作用于CPU,是重量级的,非常消耗CPU资源
    • 协程从主线程开启,是轻量级的线程,是逻辑态,对资源相对较小
    • GO语言可以轻松实现上万个协程。其他编程语言的并发机制一般基于线程,开启过多线程,对资源的消耗过大
      • 独立的栈空间
      • 共享程序堆空间
      • 调度由用户决定
    • 以主进程的退出为主

MPG模型

  • M 代表着一个内核线程,也可以称为一个工作线程。goroutine就是跑在M之上的
  • P 代表着(Processor)处理器 它的主要用途就是用来执行goroutine的,一个P代表执行一个Go代码片段的基础(可以理解为上下文环境),所以它也维护了一个可运行的goroutine队列,和自由的goroutine队列,里面存储了所有需要它来执行的goroutine。
  • G 代表着goroutine 实际的数据结构(就是你封装的那个方法),并维护者goroutine 需要的栈、程序计数器以及它所在的M等信息。
  • Seched 代表着一个调度器 它维护有存储空闲的M队列和空闲的P队列,可运行的G队列,自由的G队列以及调度器的一些状态信息等。

CPU

  • 设置Golang运行时候的CPU
    • “runtime.NumCPU” 获取CPU逻辑内核数量
    • “runtime.GOMAXPROCS”

Channel 管道

  • 我们的阶乘例子中,200个协程向map中写资源,会出现资源争夺问题,代码出现错误提示concurrent map writes

    • 解决方案1:加入全局变量互斥锁
      • lock sync.Mutex
      • 低水平的并发操作
      • 操作map前需要加锁,操作完之后解锁
      • 主线程难以确定协程的结束时间
      • 不利于对全局变量的读写操作
  • Channel 管道

    • 本质上是一个数据结构 - 队列
    • 数据是先进先出FIFO
    • 线程安全,多goroutine访问时,不需要加锁
    • 管道具有类型
    • 管道必须是引用类型
    • 必须初始化make之后才能使用
  • 管道的创建,初始化,存取数据

    // 初始化
    var intChan chan int
    intChan = make(chan int, 10)
    // 往管道赋值
    var num int
    num = 1
    intChan <- num
    // 从管道取值
    num := <- intChan
  • Channel使用的注意事项

    • channel只能存放指定类型的数据
    • channel数据放满后,就不能再放入新数据了
    • 从channel中取出数据之后,可以继续放入
    • 如果未使用协程,channel数据取完之后z再取就会报dead lock错误
  • channel 的关闭

    • 内置函数close可以关闭channel,当channel关闭后,就不能再向channel写入数据,但是可以从该channel中读取数据
  • channel的遍历

    • 使用for-range的方式来遍历channel
    • 如果channel没有关闭,则会出现死锁dead lock
    • 如果channel已经关闭,则可以正常遍历数据,遍历完成后会退出遍历
  • channel 其他注意事项

    • 可以声明为只读或者只写, 默认为双向管道
      var chan1 chan<- int // 只写
      var chan2 <-chan int // 只读
    • select 可以解决堵塞问题
      for{
            select{
                // 如果intChan一直没有关闭,不会一直堵塞而deadlock,会自动进入到下一个case
                case v := <-intChan: 
                case v := <-intChan:
                default:
            }
      }
      • recover

反射机制 Reflect

  • 基本介绍

    • 反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind)
    • 如果变量是结构体,还可以获取结构体的字段和方法
    • 通过反射,可以修改变量的值,可以调用关联的方法
    • 使用反射,需要import (“reflect”)
  • 应用场景

    • 不知道接口调用的是哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或者方法反射。
    • 对结构体的Json序列化
  • 反射的重要函数和概念

    • reflect.TypeOf(变量名)
    • reflect.ValueOf(变量名)
    • 变量,interface{}和reflect.Value可以互相转换
  • 注意事项

    • reflect.Value.Kind 获取变量的类别,返回的是一个常量
    • type是类型,kind的是类别;可能相同,也可能不同
    • 变量可以在interface{}和reflect.Value间转换
    • 使用反射的方式来获取变量的值,要求数据类型匹配
    • 通过反射来修改变量的值,当使用SetXXX()需要通过对应的指针来完成,同时需要使用到reflect.Value.Elem()方法
  • 反射的最佳实践

    • 使用反射遍历结构体的字段,调用结构的方法,并获取结构体的标签值
    • 定义两个函数test1和test2,定义一个适配器函数用作统一的处理接口
    • 操作任意的结构体
    • 创建结构体

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

Go的网络编程 上一篇
Go继续进阶 下一篇