并发:逻辑上具备同时处理多个任务的能力。 并行:物理上在同一时刻执行多个并行任务。 我们通常会说程序是并发设计的,也就是说它运行多个任务同时执行,但实际上并不一定是真的在同一时刻发生。并行是并发设计的理想模式。
多线程或多进程是并行的基本条件,但单线程也可用协程做到并发。
在Go中只需要在函数调用之前添加go关键字即可创建并发任务。
关键字go并非执行并发操作,而是创建一个并发任务单元。新建任务被放置在系统队列中,等待调度器安排合适系统线程去获取执行权。当前流程不会阻塞,不会等待该任务启动,且运行时也不保证并发任务的执行次序。
每个任务单元除保存函数指针、调用参数外,还会分配执行所需要的栈内存空间。相比系统默认MB级别的线程栈,goroutine自定义栈初始仅须2KB,所以才能创建成千上万的并发任务。自定义栈采取按需分配策略,在需要时进行扩容,最大能到GB规模。
package main import ( "fmt" "time" ) var c int func counter() int { c++ return c } func main() { a := 100 go func(x, y int) { time.Sleep(time.Second) // 让goroutine在main逻辑之后执行 fmt.Println("go:", x, y) }(a, counter()) a += 100 fmt.Println("main:", a, counter()) time.Sleep(time.Second * 3) // 等待goroutine结束 } // 输出 main: 200 2 go: 100 1进程退出时不会等待并发任务结束,可用通道(channel)阻塞,然后发出退出信号。
package main import ( "fmt" "time" ) func main() { exit := make(chan struct{}) // 创建通道 go func() { time.Sleep(time.Second) fmt.Println("gorotine done.") close(exit) // 关闭通道,发出信号 }() fmt.Println("main...") <-exit // 如关闭通道,解除阻塞 fmt.Println("main exit.") } // 输出 main... gorotine done. main exit.