天天看点

Golang从入门到精通(十八):Golang并发编程之Goroutine

进程,线程,并行和并发

一个应用程序是运行在机器上的一个进程;进程是一个运行在自己内存地址空间里的独立执行体。一个进程由一个或多个操作系统线程组成,这些线程其实是共享同一个内存地址空间的一起工作的执行体。几乎所有’正式’的程序都是多线程的,以便让用户或计算机不必等待,或者能够同时服务多个请求(如 Web 服务器),或增加性能和吞吐量(例如,通过对不同的数据集并行执行代码)。

一个并发程序可以在一个处理器或者内核上使用多个线程来执行任务,但是只有在同一个程序在某一个时间点在多个些处理内核或处理器上同时执行的任务才是真正的并行。

并行是一种通过使用多处理器以提高速度的能力。所以并发程序可以是并行的,也可以不是。

公认的使用多线程的应用最主要的问题是内存中的数据共享,它们会被多线程以无法预知的方式进行操作,导致一些无法重现或者随机的结果(称作

竞态

)。

Go

中,应用程序并发处理的部分被称作

goroutines

(go协程),它可以进行更有效的并发运算。在协程和操作系统线程之间并无一对一的关系:协程是根据一个或多个线程的可用性,映射(多路复用,执行于)在它们之上的;协程调度器在 Go 运行时很好的完成了这个工作。

Goroutine简介

Go语言中有个概念叫做goroutine, 这类似我们熟知的线程,但是更加轻量级。

我们先来看一个没有并发的例子,串行地去执行两次loop函数:

package main

import "fmt"

func loop() {
    for i :=; i <; i++ {
        fmt.Printf("%d ", i)
    }
}


func main() {
    loop()
    loop()
}
           

这里的输出是显而易见的,是

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

现在我们把一个loop放在一个goroutine里跑,我们可以使用关键字go来定义并启动一个goroutine,main()函数变为:

func main() {
    go loop()
    loop()
}
           

就加了一个 go 关键字 ,输入就变成了:

0 1 2 3 4 5 6 7 8 9

可是为什么只输出了一趟呢?明明我们主线跑了一趟,也开了一个goroutine来跑一趟啊。

原来,在goroutine还没来得及跑loop的时候,主函数已经退出了。

如何让goroutine告诉主线程我执行完毕了?使用一个信道来告诉主线程即可。

无缓冲的信道在取消息和存消息的时候都会挂起当前的goroutine,除非另一端已经准备好。如果不用信道来阻塞主线的话,主线程就会过早跑完,loop线程都没有机会执行。

本关卡需要强调的是,无缓冲的信道永远不会存储数据,只负责数据的流通。体现在:

  1. 从无缓冲信道取数据,必须要有数据流进来才可以,否则当前线阻塞
  2. 数据流入无缓冲信道, 如果没有其他goroutine来拿走这个数据,那么当前线阻塞

现在这里给出代码,具体Channel的原理和使用在本实训下一关卡Go语言Channel中详细讲解。

var complete chan int = make(chan int)

func loop() {
    for i :=; i <; i++ {
        fmt.Printf("%d ", i)
    }

    complete <- // 执行完毕了,发个消息
}


func main() {
    go loop()
    <- complete // 直到线程跑完, 取到消息. main在此阻塞住
}