并发并不是并行,并行的关键是同时做很多事情,而并发是同时管理很多事情,这些事情可能只做了一半就去做别的事情了 在很多时候,并发的效果比并行好,因为操作系统的硬件资源是有限的,很难支持操作系统同时做很多事情
程序:程序是一组指令和数据的集合,它被保存在计算机的硬盘或其他永久存储设备中,并且需要被载入到内存中才能执行。程序是静态的,它不能被直接执行,只有通过进程才能运行。
进程:进程是程序的一次执行过程,它是操作系统分配资源的基本单位,包括内存、CPU 时间片、文件句柄等。每个进程都有自己独立的内存空间,相互之间不会干扰。不同的进程之间通过进程间通信机制进行通信。
线程:线程是进程的执行单元,是操作系统调度的基本单位。一个进程可以包含多个线程,每个线程共享该进程的内存和文件句柄等资源。线程之间的切换比进程切换快得多,因为线程间的切换只需要保存和恢复一些寄存器和堆栈信息即可。
协程:协程是轻量级线程的一种实现,是用户空间的线程,不依赖操作系统的线程调度机制。协程在运行时只需保留少量的寄存器和堆栈信息,因此切换速度非常快。协程通常运行在一个线程内,多个协程之间通过协作式调度来完成任务。
联系和区别
想要使用 goroutine 的话,我们可以在函数和方法前加上go
关键字,我们可以从以下的例子入手:
package main
import (
"fmt"
"time"
)
func say(id string) {
time.Sleep(time.Second * 1)
fmt.Println("I am done! id: " + id)
}
func main() {
go say("hello")
say("goroutine")
}
这样之后,我们在运行程序会发现两个函数是同时执行的。
当然我们也可以使用匿名函数来创建一个goroutine
:
package main
import "fmt"
// 用于测试
func main() {
// 创建两个匿名函数, 用于创建goroutine
go func() {
fmt.Println("hello")
}()
go func() {
fmt.Println("goroutine")
}()
}
“正常”的结果应该是先输出hello
后输出goroutine
,或者先输出goroutine
后输出hello
。但是程序直接挂掉了,为什么?
在 Go 语言中,当main
函数结束之后,所有的goroutine
都会被暴力地终结,因为在 Go 语言中,所有的 goroutine 都是在main goroutine
的上下文中运行的。主函数没有等待这两个goroutine
完成就结束了,所以goroutine
也就没有机会执行了。
我们可以将程序修改为如下:
package main
import (
"fmt"
"sync"
)
// 用于测试
func main() {
// wg 用来等待程序的完成
var wg sync.WaitGroup
// 计数器+2 表示要等待两个goroutine
wg.Add(2)
go func() {
// 在函数退出时调用Done, wg当中的计数器-1,通知main函数该工作已经完成
defer wg.Done()
fmt.Println("hello")
}()
go func() {
defer wg.Done()
fmt.Println("goroutine")
}()
// 等待goroutine结束(计数器为0的时候)
wg.Wait()
}
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 1; i < 100; i++ {
time.Sleep(time.Second * 1)
fmt.Println("A:", i)
}
}()
go func() {
defer wg.Done()
for i := 1; i < 100; i++ {
time.Sleep(time.Second * 1)
fmt.Println("B:", i)
}
}()
wg.Wait()
}
创建两个 goroutine 并发地运行。每个 goroutine 打印一系列数字,并在打印后等待 1 秒钟。这个过程会持续大约 99 秒钟,因为循环条件是 i < 100。
由于在 main 函数中调用了 runtime.GOMAXPROCS(1),所以程序只使用一个核心来执行这两个 goroutine。因此,这两个 goroutine 会交替运行,每次只有一个 goroutine 在运行。在理论上,第二个 goroutine 可能会在第一个 goroutine 等待 1 秒钟的过程中开始运行。但是,由于调度器的复杂性和不确定性,无法准确预测 goroutine 的执行顺序。因此,我们不能确切地说第二个 goroutine 是否会在第一个 goroutine 等待 1 秒钟的过程中开始运行。