Golang如何在分隔的独立线程中运行指定的函数?通常可以使用go关键字启动一个新的执行线程goroutine。它会在一个新创建的goroutine中运行指定的函数。一个程序中的所有goroutine共享相同的地址空间。

 

例如以下代码:

go list.Sort() //通过goroutine并发执行list.Sort

 

下面的程序将打印“Hello from main goroutine”;也可能打印“Hello from another goroutine”,结果取决于哪个goroutine先完成。

func main() {
    go fmt.Println("Hello from another goroutine")
    fmt.Println("Hello from main goroutine") 

    //执行到这里时所有程序终止
    //所有活动的goroutine线程会被销毁

}

下面的代码很可能同时打印“Hello from main goroutine”和“Hello from another goroutine”。并且可能是按任何顺序打印的。

func main() {
    go fmt.Println("Hello from another goroutine")
    fmt.Println("Hello from main goroutine") 

    time.Sleep(time.Second) //休眠几秒等待另一个goroutine完成

}

下面是一个比较实际的例子,代码中定义了一个使用并发性来延迟事件的函数。

// Publish在给定的时间过期后将文本信息打印到标准输出中
// 由于使用了goroutine函数,本身不会阻塞而是会立刻返回
func Publish(text string, delay time.Duration) {
    go func() {
        time.Sleep(delay)
        fmt.Println("BREAKING NEWS:", text)
    }() //注意括号,这里调用的是匿名函数
}

接下来调用Publish函数:

func main() {
    Publish("Agoroutine starts a new thread.", 5*time.Second)
    fmt.Println("Let’s hope the news will published before I leave.")
 
    //等待goroutine中的消息发布
    time.Sleep(10 * time.Second)
    fmt.Println("Ten seconds later: I’m leaving now.")
}

程序很可能按给定的顺序打印以下三行,每一行之间约有5秒的间隔。

$ go run publish1.go
Let’s hope the news will published before I leave.
BREAKING NEWS: A goroutine starts a new thread.

Ten seconds later: I’m leaving now.

通常不建议通过休眠来协调线程使它们彼此等待。Go的主要同步方法仍然是使用通道。

 

结论

goroutine是轻量级的,成本仅比堆栈空间的分配高一点点。栈开始很小,然后根据需要分配和释放堆存储空间,从而不断增长。线程内部的goroutine就像协程一样,会在多个操作系统线程之间进行多路复用。如果一个goroutine阻塞了一个OS线程,例如等待输入,那么这个线程中的其他goroutine将迁移,以便它们可以继续运行。

 

问题思考:

为什么要基于CSP的思想构建并发性?

随着时间的推移,对于并发性和多线程编程非常困难的这样一种认知似乎越来越普遍。主要原因一部分是由于复杂的设计,另一部分是由于过分强调底层细节(如互斥、条件变量和内存屏障)。更高级的接口支持更简单的代码,即使仍然存在互斥锁之类的东西。

 

为并发性提供高级语言支持的最成功的模型之一来自于Hoare的通信顺序流程模型(CSP)。Occam和Erlang是源于CSP的两种著名语言。Go的并发原语来自于家族树的另一部分,其主要特点是将通道(Channel)作为语言中处理并发的主要概念。几种早期语言的经验表明,CSP模型非常适合类似Go这样的过程语言。

 

为什么使用Goroutine而不是thread?

Goroutine使得并发编程更易于开发者使用。这个理念其实已经存在很长一段时间了,它是将独立执行的函数(协程)多路复用到一组线程上。当协同程序(即goroutine)阻塞时,例如调用阻塞IO系统,运行时自动将同一操作系统线程上的其他协同程序移动到不同的、可运行的线程,这样它们就不会被阻塞。重点是所有过程对开发者透明。goroutines的性能非常高,除了堆栈的内存(只有几kb)之外,它们几乎没有开销。

 

为了使堆栈更小,Go的运行时使用可调整大小的有界堆栈。一个新生产的goroutine被赋予几个kb,一般来说这点内存已经足够了。如果需要更多内存,则运行时会自动增加(或减少)存储堆栈的内存,从而允许许多goroutine驻留在有限的内存中。因此在同一个地址空间中创建成千上万的goroutine是可行的。如果goroutine只是线程,那么系统资源不足以支撑大规模的线程数量,很快就会耗尽。

 

原文作者:yourbasic.org       译者:江玮