进程、线程与并行、并发
进程
进程(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在同一时刻准备好执行发送和接收。当发送和接收阻塞时也有不同的条件。只有当通道中没有要接收的值时,接收才会阻塞。仅当没有可用缓冲区来放置正在发送的值时,发送才会阻塞。
通道的发送和接收特性
- 对于同一个通道,发送操作之间是互斥的。接收操作之间也是互斥的。
- 发送操作和接收操作中对元素值的处理都是不可分割的。
- 发送操作在完全完成之前会被阻塞。接收操作也是如此。
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都结束
}