go-并发、协程、chan | 青训营

122 阅读2分钟

进程、线程与并行、并发

进程

进程(Process)就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位,进程是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间。一个进程至少有5种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。

以上为书本上的说法,而用通俗的话来说进程就是一个正在执行的程序。

线程

线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位

一个进程可以创建多个线程,同一个进程中多个线程可以并发执行

一个线程要运行的话,至少有一个进程

并发与并行

并发:多个线程同时竞争一个位置,竞争到的才可以执行,每一个时间段只有一个线程在执行。

并行:多个线程可以同时执行,每一个时间段,可以有多个线程同时执行。

通俗的讲多线程程序在单核CPU上面运行就是并发,多线程程序在多核CUP上运行就是并行,如果线程数大于CPU核数,则多线程程序在多个CPU上面运行既有并行又有并发

goroutine

Golang中的并发是函数相互独立运行的能力。Goroutine是并发运行的函数。Golang提供了Goroutine作为并发处理操作的一种方式。

创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字,比如有函数命名为func1 那么启动协程的代码即go func1()

Example

注:例子都省略了package 和 相关包的import

func show(msg string) { 
    for i := 1; i < 5; i++ { 
        fmt.Printf("msg: %v\n", msg) 
        time.Sleep(time.Millisecond * 100) 
    } 
} 
 
func main() { 
    go show("java") 
    show("golang") // 在main协程中执行,如果它前面也添加go,程序没有输出 
    fmt.Println("end...") // 主函数退出,程序就结束了 
}

通道


go使用通道在goroutine之间共享数据。通道充当goroutine之间的管道(管道)并提供一种机制来保证同步交换。

需要在声明通道之前指定数据类型。数据在通道上传递:在任何时间只有一个goroutine可以访问数据项,因此不会发生数据竞争

根据数据交换的行为,分为:无缓冲通道(用于执行goroutine之间的同步通信)和缓冲通道(用于执行异步通信)。无缓冲通道保证在发送和接受发生的瞬间两个goroutine之间的交换。缓存通道无保证

语法

使用内置函数make创建无缓冲和缓存通道,make的第一个参数是关键字chan, 然后是通道允许交换的数据类型:

ch1 := make(chan int)      // 无缓冲通道
ch2 := make(chan int, 3)   // 有缓冲通道
ch3 := make(chan<- int, 1) // 单向通道:只能发送不能接收
ch4 := make(<-chan int, 1) // 单向通道:只能接收不能发送

将数据送入通道使用运算符<-

g1 <- "Australia" // 发送字符串 ,g1为字符串缓存通道

接受数据(运算符<- 放在通道变量左侧,来接受通道内的数据):

data := <- g1

无缓冲通道

在无缓冲通道中,在接收到任何值之前没有能力保存它。在这种类型的通道中,发送和接收goroutine在任何发送或接收操作完成之前的同一时刻都准备就绪。如果两个goroutine没有在同一时刻准备好,则通道会让执行其各自发送或接收操作的goroutine首先等待。同步是通道上发送和接收之间交互的基础。没有另一个就不可能发生。

缓冲通道

在缓冲通道中,有能力在接收到一个或多个值之前保存它们。在这种类型的通道中,不要强制goroutine在同一时刻准备好执行发送和接收。当发送和接收阻塞时也有不同的条件。只有当通道中没有要接收的值时,接收才会阻塞。仅当没有可用缓冲区来放置正在发送的值时,发送才会阻塞。

通道的发送和接收特性

  1. 对于同一个通道,发送操作之间是互斥的。接收操作之间也是互斥的。
  2. 发送操作和接收操作中对元素值的处理都是不可分割的。
  3. 发送操作在完全完成之前会被阻塞。接收操作也是如此。

Example

var values = make(chan int) 
 
func send() { 
    rand.Seed(time.Now().UnixNano()) 
    value := rand.Intn(10) 
    fmt.Printf("send: %v\n", value) 
    values <- value 
} 
 
func main() { 
    defer close(values) 
    go send() 
    fmt.Println("wait...") 
    value := <-values 
    fmt.Printf("receive: %v\n", value) 
    fmt.Println("end...") 
}

基本特点

元素复制

进入通道的并不是接受操作符右边的那个元素值,而是它的副本 发送操作:是“复制元素值”和”放入通道“两步 接受操作:“复制通道内的元素值”—>“放置副本到接受方”—>”删掉原值“

不可分割

一个数据进入通道时,不会存在还没有复制完毕就被接受的情况

WaitGroup

属于package sync。用来做任务编排的一个并发原语。它要解决的就是并发-等待问题

基本用法

  • Add: 用于设置WaitGroup的计数值
  • Done: 用来将WaitGroup的计数值-1,其实就是调用类的Add(-1)
  • Wait:goroutine调用了这个方法会一直阻塞,知道WaitGroup的计数值变为0

Example

 
var wg sync.WaitGroup 
 
func hello(i int) { 
    defer wg.Done() // gorontine结束就登记-1 
    fmt.Printf("i: %v\n", i) 
} 
 
func main() { 
    for i := 0; i < 10; i++ { 
        wg.Add(1) // 启动一个goroutine就登记+1 
        go hello(i) 
    } 
    wg.Wait() // 等待所有登记的goroutine都结束 
}