进程和线程以及go语言中的协程

进程

进程(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("关闭协程")
}