//1 并发和并行
并发:同一时间段内,多个任务在执行(单个cpu,执行多个任务)
并行:同一时刻,多个任务在执行(多个cpu的支持)
//注:
编程语言中,因为Python有GIL全局解释器锁,导致同一时刻,同一个进程中只能运行一个线程
===> 延伸出开启多进程,解决利用上多核优势
其他语言并发,不存在开启多进程一说,直接开启多线程,就会利用上多核优势
go并发,没有开启多进程、多线程一说,直接开启go协程 goroutine //实质是协程 + 线程池//2 go协程 goruotine 的开启方法
任务函数调用前 加关键字 go
eg: go test()
//3 大小
协程(goroutine) : 2kb大小
线程 : 几兆(M)
//go协程会复用线程,就是GMP模型中,从线程池的一堆线程M中,通过Processor调度器P,去挂载执行go的协程G//4 协程通信
goroutine之间通信,通过 信道channel 通信
go推崇用信道通信,而不推崇用共享变量通信(锁,死锁)
//5 go语言的GMP模型
-G: 开的goroutine
-M: M当成操作系统真正的线程,实际上是用户线程,再对应到内核线程(操作系统线程)
-P: Processor:goroutine的调度器,是一个队列
gomaxprocs默认是cpu核数(可以当做cpu核数)
//用户线程 与 内核线程 的映射关系
python : 开的线程是用户线程,用户线程跟内核线程 是1:1的对应关系
某些语言: 用户线程和内核线程 是n:1的关系
go : 用户线程和内核线程 是n:m的关系 //m < n,一般把m设置为CPU核数package main
import (
"fmt""runtime""time"
)
functest() {
fmt.Println("go go go")
}
funcmain() {
fmt.Println("主线程开始执行")
go test() //方法调用前 加关键字 go --> 开启一个goroutinego test()
go test()
go test()
go test()
go test()
go test()
go test()
time.Sleep(1*time.Second)
fmt.Println("主线程结束执行")
//go语言中,主线程不会等待goroutine执行完成,要等待它结束需要自己额外处理
}
funcmain() {
fmt.Println("主线程开始执行")
for i:=0;i<10;i++ {
gofunc(){
fmt.Println("go go go ")
}()
}
time.Sleep(1*time.Second)
fmt.Println("主线程结束执行")
}
//eg: GMP模型funcmain() {
// 设置P的大小,认为是cpu核数即可
runtime.GOMAXPROCS(1)
fmt.Println("主线程开始执行")
gofunc() {
for {
fmt.Println("xxxx")
}
}()
for i := 0; i < 10; i++ {
gofunc() {
for {
fmt.Println("我是死循环")
}
}()
}
time.Sleep(10 * time.Second)
fmt.Println("主线程结束执行")
}
2 信道(通道)
2.1 基本信道
//不同goroutine之间通信,通过信道channel实现
信道 其实在内存中也是一个共享变量,只不过是处理了线程安全(就是自动有加锁的处理)
package main
import (
"fmt""time"
)
funcmain() {
//1 定义channelvar c chanint//定义了一个int类型的信道,协程往里存取数字//2 信道的零值
引用类型,空值为nil
当做参数传递时,不需要取地址,改的就是原来的
需要初始化再使用
fmt.Println(c)
//3 信道初始化
c=make(chanint) //数字暂时先不关注//4 信道放值 (注意赋值和放值)
c <- 1
c = 12//赋值直接报错//5 信道取值
<- c
//6 取出来赋值给一个变量 intvar a int
a = <- c
a := <- c //推导类型: 定义变量a//7 信道默认情况下,不管放值还是取值,都是阻塞的
必须是至少有两个协程 同时对接存取值时,放取值的协程 才不阻塞,继续向下执行
若只是有单个放值 或 取值的协程,会报错死锁: deadlock
go test1(c) //开启了一个协程,执行test1函数,向信道c放值 10
b := <- c //阻塞 不但实现了两条协程之间通信,还实现了等待协程执行结束
fmt.Println(b)
}
functest1(a chanint) {
time.Sleep(1*time.Second)
fmt.Println("go go go ")
//往信道中放一个值
a <- 10//阻塞
}
2.2 信道小案例
//信道小例子
程序有一个数中 每一位的平方和与立方和,然后把平方和与立方和相加并打印出来
package main
import (
"fmt""time"
)
funccalcSquares(number int, squareop chanint) {
sum := 0//总和for number != 0 {
digit := number % 10//589对10取余数,9 8 5
sum += digit * digit //sum=9*9 8*8 5*5
number /= 10//num=58 5 0
}
time.Sleep(2 * time.Second)
squareop <- sum
}
funccalcCubes(number int, cubeop chanint) {
sum := 0for number != 0 {
digit := number % 10
sum += digit * digit * digit
number /= 10
}
time.Sleep(1 * time.Second)
cubeop <- sum
}
funcmain() {
ctime := time.Now().Unix()
fmt.Println(ctime)
number := 589
sqrch := make(chanint)
cubech := make(chanint)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
//squares:= <-sqrch//cubes:=<-cubech
fmt.Println("Final output", squares+cubes)
ttime := time.Now().Unix()
fmt.Println(ttime)
fmt.Println(ttime - ctime)
}
2.3 信道死锁、单向信道、关闭信道
//1 信道的死锁现象 deadlock
默认在多个goroutine中,放值、取值都是阻塞的
在一个goroutine中,出现若只是有单个放值 或 取值的,就会出现死锁
package main
import"fmt"//信道死锁eg:funcmain() {
var c chanint = make(chanint)
c<-1//其实放不进去,阻塞在这,就死锁了
<-c //没有,取不到,阻塞在这,就死锁了
}
//2 单向信道:只写 或 只读 chan<- 了解funcsendData(sendch chan<- int) {
sendch <- 10
}
funcmain() {
//sendch := make(chan<- int) //定义了一个只写信道
sendch := make(chanint) //定义了一个可读可写信道go sendData(sendch) //传到函数中转成只写信道,在goroutine中,只负责写,不能往外读,主协程读
fmt.Println(<-sendch) //只写信道一旦读,就有问题
}
///3 关闭信道funcmain() {
sendch := make(chanint)
//关闭信道close(sendch)
//信道可以用for循环遍历
}
//for循环 遍历信道,如果不关闭会报死锁,如果关闭了,放不进去,循环结束funcproducer(chnl chanint) {
for i := 0; i < 100; i++ {
fmt.Println("放入了",i)
chnl <- i
}
close(chnl)
}
funcmain() {
ch := make(chanint)
go producer(ch)
for v := range ch { // for range 遍历循环
fmt.Println("Received ",v)
}
}
//1 使用锁的场景
多个goroutine通过 共享内存(共享变量) 实现数据通信
//2 临界区
当程序并发地运行时,多个[Go 协程]同时修改共享资源的代码。这些修改共享资源的代码称为临界区。
//如果在任意时刻只允许一个 Go 协程访问临界区,那么就可以避免竞态条件。而使用 Mutex 可以达到这个目的//3 总结1.不同goroutine之间传递数据:共享变量、通过信道
2.如果是修改共享变量,建议加锁
3.如果是协程之间通信,用信道
package main
import (
"fmt""sync"
)
==================================================================
//含有竞态条件的程序: 1000个协程,每个协程都是 x += 1 的操作var x = 0funcincrement(wg *sync.WaitGroup) {
x = x + 1
wg.Done()
}
funcmain() {
var w sync.WaitGroup
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w)
}
w.Wait()
fmt.Println("final value of x", x)
}
//eg: 通过共享变量 + 互斥锁var x = 0//全局变量,各个goroutine都可以拿到并且操作funcincrement(wg *sync.WaitGroup,m *sync.Mutex) {
m.Lock()
x = x + 1
m.Unlock()
wg.Done()
}
funcmain() {
var w sync.WaitGroup
var m sync.Mutex //定义一个全局锁,锁是个值类型,函数传递需要传地址
fmt.Println(m)
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w,&m)
}
w.Wait()
fmt.Println("final value of x", x)
}
==================================================================
//eg: 通过信道来做var x = 0funcincrement(wg *sync.WaitGroup, ch chanbool) {
ch <- true//缓冲信道放满了,就会阻塞 等到信道数据被取出后,才会执行放值
x = x + 1
<- ch
wg.Done()
}
funcmain() {
var w sync.WaitGroup
ch := make(chanbool, 1) //定义了一个有缓存,容量大小为1的信道for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w, ch)
}
w.Wait()
fmt.Println("final value of x", x)
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异