• 关于go中并发的初步理解


    1.一些概念的介绍:

    概念 描述
    进程

    在内存中的程序。有自己独立的独占的虚拟 CPU 、虚拟的 Memory、虚拟的 IO devices。

    (1) 每一进程占用独立的地址空间。 此处的地址空间包括代码、数据及其他资源。
    (2) 进程间的通信开销较大且受到许多限制。 对象(或函数)接口、通信协议
    (3) 进程间的切换开销也较大。 又称Context Switch。上下文包括代码、数据、堆栈、处理器状态、资源。

    线程

    轻量级进程。在现代操作系统中,是进程中程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。

    (1) 多个线程共享进程的地址空间(代码、数据、其他资源等)。
    线程也需要自己的资源,如程序计数器、寄存器组、调用栈等。

    (2) 线程间的通信开销较少且比较简单。
    因为共享而减少了需要通信的内容。
    但也因为充分共享而无法对共享资源进行保护。

    (3) 线程间的切换开销也较小。
    只需保存每一线程的程序计数器、寄存器组、堆栈等空间。
    不必切换或复制整个地址空间,从而成本大为降低(约1/10)

    协程 轻量级线程。 是可以并发执行的函数,由编译或用户指定位置将控制权交给协程调度程序执行的方式。它是非抢占式的,可以避免反复系统调用,还有进程切换造成的开销,给你上几千个逻辑流,也称用户级别线程。
    逻辑处理器 每个逻辑处理器都会绑定一个线程,并负责goroutine的执行
     并发  并发是以时间段为维度,即在单位时间内,同时完成多件事。
     并行  并行是以时间点为维度,即在某个时间点,同时完成多件事。
     同步与异步  同步和异步针对应用程序而言,关注的是程序中间的协作关系,一般用于应用程序与内核的交互,同步即等待或是轮询;异步则是直接返回,完成后通知应用程序。
     阻塞与非阻塞  阻塞与非阻塞关注的是单个进程的执行状态,一般用于网络io中,阻塞即需要等待,不会立即返回;非阻塞则会立刻返回。
     上下文  上下文是一种非常泛化的概念,可以理解为程序执行的环境变量。
       
       

    2.关于并发和并行的理解

    解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
    解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
    解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群

    3.并发实例:

    每当执行go func,就会创建一个goroutine,在Go语言中,goroutine就是协程。每个goroutine的结构体中有一个sched域就是用于保存自己上下文的。这样,goroutine就可以被换出去,再换进来。这种上下文保存在用户态完成,不必陷入到内核,非常的轻量,切换速度很快。有的协程运行到一定时候就主动调用yield放弃自己的执行,把自己再次放回到任务队列中等待下一次调用时机等。

    func trace(start, end int8) {
       for i:=start; i<=end;  i++{
          fmt.Printf("%c ", i)
          time.Sleep(time.Second)
       }
    }
    
    func main() {
       runtime.GOMAXPROCS(1) //限制只有一个逻辑处理器
       var wg sync.WaitGroup   //用于等待所有协程都完成
       wg.Add(2)
    
       go func(){
          defer wg.Done()//程序退出的时候执行
          trace('a', 'f')
       }()
    
       go func(){
          defer wg.Done()//程序退出的时候执行
          trace('A', 'F')
       }()
    
       wg.Wait() //等待所有协程的完成
    }

    上面的程序使用runtime.GOMAXPROCS(1)来分配一个逻辑处理器供调度器使用,两个goroutine将被该逻辑处理器调度并发执行。输出如下:

    A a b B C c d D E e f F G g h H I i j J

    在go语言中,“有函数调用,就有机会被调度器调度”,在上面案例中trace方法里面调用了time.sleep()函数的目的,就是让当前运行goroutine有机会被调度器调度,进剥夺该goroutine的执行权,让其他的goroutine执行。所以上面代码打印的结果是大小写字母,交替的输出。如果注销掉”time.sleep()”,输出结果为:

    A B C D E F G H I J a b c d e f g h i j

    另一个比较好的例子:

    package main
     
    import (
        "fmt"
        "sync"
    )
     
    //声明一个全局变量
    var waitgroup sync.WaitGroup
     
    func Afunction(shownum int) {
        fmt.Println(shownum)
        waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
    }
     
    func main() {
        for i := 0; i < 10; i++ {
            waitgroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
            go Afunction(i)
        }
        waitgroup.Wait() //.Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
    }

    4.并发通信

    只使用wg.add和wg.Wait显然是不能满足我们的需要的,这时候就需要用到go中的goroutines和通道channel处理并发了。在Go中,所有I/O都被阻塞,通过goroutines和通道channel处理并发,而不是回调和异步。通道channel主要负责在并发过程中,实现通信。通道channel是类型相关的,一个通道channel只能传递一种数据类型的值。

    参考:http://www.findme.wang/blog/detail/id/427.html

  • 相关阅读:
    js格式化时间和时间操作
    java链接FTP实现上传和下载
    JQuery 对 Select option 的操作
    利用set实现去重
    数组去重的五个办法
    JavaScript实现360度全景图片展示效果
    对于行高(line-height)的一些理解
    Flex 布局教程:语法篇
    学习Javascript闭包
    MySQL explain字段解释
  • 原文地址:https://www.cnblogs.com/zheng123/p/9803014.html
Copyright © 2020-2023  润新知