进程
进程(Process)就是程序在操作系统中的一次执行的过程,是系统进行资源分配和调度的基本单位。
进程是一个动态概念,是程序在执行过程中分配和管理资源的基本单位。
每一个进程都有一个自己的地址空间。一个进程至少有五种基本状态。
它们是:初始状态,执行状态,等待状态,就绪状态,终止状态。
通俗的讲,进程就是一个正在执行的程序。
线程
线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。
一个进程可以创建多个线程,同一个进程中的多个线程可以并发执行,一个程序要运行的话,至少有一个进程。
并发和并行
并发:多个线程同时竞争一个位置,竞争到的才可以执行,每一个时间段只有一个线程在执行。
并行:多个线程可以同时执行,每一个时间段,可以有多个线程同时执行。
通俗的讲多线程程序在单核CPU上面运行就是并发,多线程程序在多核CUP上运行就是并行。
通俗的讲多线程程序在单核CPU上面运行就是并发,多线程程序在多核CUP上运行就是并行。
如果线程数大于CPU核数,则多线程程序在多个CPU上面运行既有并行又有并发
golang中的主线程
golang中的主线程:(可以理解为线程/也可以理解为进程),
在一个Golang程序的主线程上可以起多个协程。
Golang中多协程可以实现并行或者并发。
协程
协程:可以理解为用户级线程,这是对内核透明的,也就是系统并不知道有协程的存在,
是完全由用户自己的程序进行调度的。
Golang的一大特色就是从语言层面原生支持协程,在
函数或者方法前面加go关键字就可创建一个协程。可以说Golang中的协程就是
这种写法是按照顺序执行的哈
package main
import (
"fmt"
"time"
)
func TextTime() {
for i := 0; i < 10; i++ {
fmt.Println("TextTime()方法,你好golang")
time.Sleep(time.Millisecond * 50)
}
}
func main() {
TextTime()
for i := 0; i < 10; i++ {
fmt.Println("main()方法,你好golang")
// 等待50毫秒
// 这里使用了time.Sleep()函数,该函数可以将当前线程暂停执行,等待指定的时间,然后输出"你好golang"
time.Sleep(time.Millisecond * 50)
}
}
上面的代码,是按照顺序进行执行的哈。
先执行完TextTime()方法中的循环,再执行main()方法中的循环。
也就是说:是同步执行的哈。
并行执行需求:
在主线程(可以理解成进程)中,开启一个goroutine,该协程每隔50毫秒秒输出"你好golang"
在主线程中也每隔50毫秒输出"你好golang",输出10次后,退出程序,
要求主线程和goroutine同时执行
开启协程 go 方法名()
package main
import (
"fmt"
"time"
)
func TextTime() {
for i := 0; i < 10; i++ {
fmt.Println("TextTime()方法,你好golang")
time.Sleep(time.Millisecond * 150)
}
}
func main() {
// 开启一个协程
go TextTime()
for i := 0; i < 10; i++ {
fmt.Println("main()方法,你好golang")
// 等待50毫秒
// 这里使用了time.Sleep()函数,该函数可以将当前线程暂停执行,等待指定的时间,然后输出"你好golang"
time.Sleep(time.Millisecond * 50)
}
}
现在,man方法中的方法和TextTime()方法是一起执行的哈。
现在这个写法有点问题;
因为主线程执行完毕之后,无论协程是否执行完毕,都会退出程序。
这样会导致,协程不会全部执行完毕就会退出。
我们想让协程执行完毕后,在执行主进程退出。
这个时候我们可以使用 sync.WaitGroup 等待协程执行完毕
sync.WaitGroup 实现主线程等待协程执行完毕
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup // wg变量的类型是sync.WaitGroup
func TextTime() {
defer wg.Done() // 不管函数正常返回还是 panic(崩溃),都会执行
for i := 0; i < 10; i++ {
fmt.Println("TextTime()方法,你好golang")
time.Sleep(time.Millisecond * 150)
}
}
func main() {
// 声明要等待 1 个 goroutine
wg.Add(1)
go TextTime()
for i := 0; i < 10; i++ {
fmt.Println("main()方法,你好golang")
// 等待50毫秒
// 这里使用了time.Sleep()函数,该函数可以将当前线程暂停执行,等待指定的时间,然后输出"你好golang"
time.Sleep(time.Millisecond * 50)
}
// 等待携程执行完毕
wg.Wait()
}
这样携程执行完毕之后,主线程才会退出。
wg.Add(1) wg.Done() wg.Wait()是什么意思?
这三个是 Go 里 sync.WaitGroup 的经典组合,用来等待 goroutine 执行完,避免主程序提前退出。
wg.Add(1) 告诉 WaitGroup:有 1 个任务需要等待
wg.Done() 任务完成,内部计数 减 1
wg.Wait() 阻塞主 goroutine,直到计数变为 0
上面的代码执行流程是:
wg.Add(1) // 计数 = 1 通常放置在启动子 goroutine 之前
go TextTime() // 启动子 goroutine
... // main 继续执行自己的循环
wg.Wait() // main 在这里卡住,等计数变 0
wg.Done() // TextTime 执行完,计数 1→0,wg.Wait() 放行
程序结束
如果去掉 wg.Wait(),
main 函数跑完自己的 10 次循环就直接退出了,TextTime() 可能还没跑完就被杀掉了。
defer wg.Done() 的作用
我们知道 wg.Done() 是任务完成,内部计数 减 1
defer wg.Done() 的作用: 不管 TextTime() 是否正常执行完,还是中间发生了 panic(崩溃),
wg.Done() 都会被执行。
这样就不会因为某个 goroutine 崩溃了,
导致 wg.Done() 没调用,wg.Wait() 永远等下
不管函数正常返回还是 panic,都会执行 这个 panic是什么意思
panic 就是 Go 里的程序崩溃 / 严重错误,类似于其他语言里的"异常"。
获取当前CPU的个数
package main
import (
"fmt"
"runtime"
)
func main() {
// 获取当前CPU的个数
cpuNum := runtime.NumCPU()
fmt.Println("当前CPU的个数是:", cpuNum)
}
输出:当前CPU的个数是: 14
设置程序使用的CPU个数
package main
import (
"fmt"
"runtime"
)
func main() {
// 获取当前CPU的个数
cpuNum := runtime.NumCPU()
fmt.Println("当前CPU的个数是:", cpuNum)
// 设置程序使用的CPU个数
runtime.GOMAXPROCS(cpuNum - 1)
fmt.Println("Ok")
}
输出:Ok
通过for循环一次开启多个协程
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func Test(num int) {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Printf("协程(%v),这是第%v次循环\n", num, i+1)
}
}
func main() {
for i := 1; i <= 5; i++ {
wg.Add(1)
go Test(i)
}
wg.Wait()
fmt.Println("关闭协程")
}