关于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

原文地址:https://www.cnblogs.com/zheng123/p/9803014.html