Processes and Threads(线程和进程)
首先,先了解下,什么是线程? 什么是进程?
比较抽象的概念为: ”进程是资源分配的最小单位,线程是CPU调度的最小单位“
这样的解释似乎不太容易让人理解.
可以做一个简单的比喻:
进程==火车,线程==车厢
- 线程在进程下行进(单纯的车厢无法运行)
- 一个进程可以包含多个线程(一辆火车可以有多个车厢)
- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
- 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
这样理解,可以更加清晰.
参考:https://www.zhihu.com/question/25532384
了解完成进程和线程的区别后,我们继续并发和并行的概念?
并行发并行理解
看了很多文章和视频,似乎我之前对并发和并行的概念也很模糊,甚至越绕越晕;
直到看到了Erlang之父Joe Armstrong 画的一张图.
并发: 两个队列交替使用一台咖啡机
并行: 两个队列同时使用两台咖啡机
这样就好理解了~
另外还有一些疑问:
并发是一个线程? 并行是多个线程?
答: 并发和并行都可以是多个线程,就看这些线程能不能同时被多个cpu执行,如果可以就说明是并行,而并发是多个线程被一个cpu轮流去执行。
并发程序并不要求cpu具备多核计算能力。在同一时间段内,多个线程会被分配一定的执行时间片,在cpu上被快速轮换执行。
而并行程序要求CPU提供多核并行计算的能力。同一时刻内,就有多个线程CPU上的多个核上同时执行指令。
了解了上面的知识后,我们继续了解本章节的goroutine.
goroutine
什么是goroutine?
Go 语言层面支持的 go 关键字,可以快速的让一个函数创建为 goroutine,我们可以认为 main 函数就是作为 goroutine 执行的。操作系统调度线程在可用处理器上运行,Go运行时调度 goroutines 在绑定到单个操作系统线程的逻辑处理器中运行(P)。即使使用这个单一的逻辑处理器和操作系统线程,也可以调度数十万 goroutine 以惊人的效率和性能并发运行。
Go语言的最大特色就是从语言层面支持并发,
参考: https://www.zhihu.com/question/33515481
简单了解goroutine的用法.
package main
import (
"fmt"
"time"
)
func say(s string) {
for i:=0; i <3; i ++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("hello world")
time.Sleep(1000 * time.Millisecond)
fmt.Println("over")
}
在上述代码中,为什么我们需要在main函数中去sleep等待呢?
因为如果不进行等待阻塞,main函数会由上至下顺序执行,可能无法执行say()方法,主进程就已经退出了!
那如果希望等待当前的 goroutine 执行完成,然后再接着往下执行,该怎么办?本文尝试介绍这类问题的解决方法。
package main
import (
"time"
"fmt"
)
func say(s string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("hello world")
fmt.Println("over!")
}
上述代码会输出 over!
因为goroutine以非阻塞的方式执行,他们会随着程序(主线程)的结束而消亡,这肯定不是我们需要的结果.
2改进版本:
func main() {
go say("hello world")
time.Sleep(1000 * time.Millisecond)
fmt.Println("over!")
}
这个是属于比较low的做法,最简单,直接,通过Sleep函数,等待goroutine执行完成。
3 使用channel
func main() {
done := make(chan bool) // 定义一个阻塞的chan
go func() {
for i:=0; i<3; i ++ {
time.Sleep(100 *time.Millisecond)
fmt.Println("hello world")
}
fmt.Println("发送done信号")
done <- true // 当go func 执行完成后, 向channel发送一个信号
}()
fmt.Println("开始接受done信号")
<- done // 收到信号后,才会继续往下走,如果没有收到done的信号,则一直阻塞
fmt.Println("接受完成done信号")
fmt.Println("over")
}
4 标准答案
Go 官方在sync包中提供了 WaitGroup类型来解决这个问题.
A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.
等待组等待goroutine集合完成。主goroutine调用Add可设置要等待的goroutine数。然后每个goroutine都会运行,并在完成后完成调用。同时,可以使用Wait阻塞,直到所有goroutine都完成
对于waitGroup的使用可以总结为以下几点:
- 创建一个waitgroup实例,比如名称为wg
- 调用 wg.Add(n),其中:n 是等待的goroutine的数量
- 在每个goroutine运行的函数中执行 defer wg.Done()
- 调用 wg.Wait() 阻塞主逻辑
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
say2("hello", &wg)
say2("world", &wg)
fmt.Println("over!")
}
func say2(s string, waitGroup *sync.WaitGroup) {
defer waitGroup.Done()
for i := 0; i < 3; i++ {
fmt.Println(s)
}
}
5 空的select 将永远阻塞
Keep yourself busy or do the work yourself
func main(){
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Println("hello world")
})
go func() {
if err := http.ListenAndServe(":8080",nil); err != nil {
log.Fatal(err)
}
}()
select {
}
}
6 规范
- 在绝大多数情况下,建议我们把go的并发逻辑交给调用者
2 .我们在使用并发时,一定要有能力去控制gouroutine的生命周期
参考地址:
https://www.cnblogs.com/sparkdev/p/10917536.html