网站建设联系方式,杭州 定制网站,icp备案证书,最新企业名录免费go并发编程 一、 并发介绍1#xff0c;进程和线程2#xff0c;并发和并行3#xff0c;协程和线程4#xff0c;goroutine 二、 Goroutine1#xff0c;使用goroutine1#xff09;启动单个goroutine2#xff09;启动多个goroutine 2#xff0c;goroutine与线程3#xff0… go并发编程 一、 并发介绍1进程和线程2并发和并行3协程和线程4goroutine 二、 Goroutine1使用goroutine1启动单个goroutine2启动多个goroutine 2goroutine与线程3goroutine调度 三、runtime包1runtime.Gosched()2runtime.Goexit() 退出当时协程3 runtime.GOMAXPROCS4将任务分配到不同的CPU逻辑核心上实现并行5Go语言中的操作系统线程和goroutine的关系 四、channel1CSP模型2channel 类型3创建channel4channel 操作1发送2接收3关闭 5无缓冲通道6有缓冲通道7,从通道中遍历获取值8单向通道8通道总结 一、 并发介绍
1进程和线程
A. 进程是程序在操作系统中的一次执行过程系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行
2并发和并行
A. 多线程程序在一个核的cpu上运行就是并发。B. 多线程程序在多个核的cpu上运行就是并行。
并发 并行
3协程和线程
协程独立的栈空间共享堆空间调度由用户自己控制本质上有点类似于用户级线程这些用户级线程的调度也是自己实现的。线程一个线程上可以跑多个协程协程是轻量级的线程。
4goroutine
goroutine 只是由官方实现的超级线程池。 每个实力4~5KB的栈内存占用和由于实现机制而大幅减少的创建和销毁开销是go高并发的根本原因 goroutine 奉行通过通信来共享内存而不是共享内存来通信。
二、 Goroutine
Go语言引入了goroutine机制简化了并发编程。程序员只需定义任务函数通过开启goroutine实现并发执行而无需自己管理线程池、任务调度和上下文切换。 Go运行时负责智能分配任务到CPU将复杂性隐藏在底层。 这使得Go成为现代化编程语言使并发编程更加简单和高效。
1使用goroutine
在函数或匿名函数 前面添加 go 关键词。一个goroutine必定对应一个函数可以创建多个goroutine去执行相同的函数。
1启动单个goroutine
func hello() {fmt.Println(Hello Goroutine!)
}
func main() {go hello()fmt.Println(main goroutine done!)
}//但打印没有 Hello Goroutine! 因为main 生命周期结束goroutine 还没启动//让main 等等 goroutine 粗暴方法func main() {go hello() // 启动另外一个goroutine去执行hello函数fmt.Println(main goroutine done!)time.Sleep(time.Second)
}2启动多个goroutine
使用了sync.WaitGroup来实现goroutine的同步
var wg sync.WaitGroupfunc hello(i int) {defer wg.Done() // goroutine结束就登记-1fmt.Println(Hello Goroutine!, i)
}
func main() {for i : 0; i 10; i {wg.Add(1) // 启动一个goroutine就登记1go hello(i)}wg.Wait() // 等待所有登记的goroutine都结束
}打印出来并不是顺序因为这是因为10个goroutine是并发执行的而goroutine的调度是随机的。2goroutine与线程
可增长栈 OS线程操作系统线程一般都有固定的栈内存通常为2MB,一个goroutine的栈在其生命周期开始时只有很小的栈典型情况下2KBgoroutine的栈不是固定的他可以按需增大和缩小goroutine的栈大小限制可以达到1GB虽然极少会用到这个大。所以在Go语言中一次创建十万左右的goroutine也是可以的。
3goroutine调度
Go语言的运行时runtime引入了GPM模型来实现并发调度与传统操作系统调度不同。 Ggoroutine G是goroutine的缩写代表一个任务单元。它存储了该任务的信息以及与所在P处理器的关联。 P处理器 P管理一组goroutine队列包含当前goroutine的运行上下文。P负责调度自己的队列比如暂停耗时长的任务、切换到其他任务。当P队列为空它会从全局队列取任务甚至从其他P队列抢占任务。 M机器 M是Go运行时对操作系统内核线程的虚拟。通常是一一对应的关系每个M执行一个goroutine。当一个G长时间阻塞在一个M上会创建新的M将其他G挂载在新M上。旧M释放后用于回收资源。 GOMAXPROCS 用于设定P的个数控制并发度但不会过多地增加P和M以避免频繁切换的开销。
Go语言与其他语言不同之处在于它在运行时实现了自己的调度器使用m:n调度技术。这意味着goroutine的调度发生在用户态避免了内核态与用户态的频繁切换包括内存分配与释放都在用户态维护性能开销较小。此外Go语言充分利用多核硬件资源将多个goroutine均匀分配在物理线程上加上goroutine的轻量特性保证了高效的并发调度性能。
三、runtime包
1runtime.Gosched()
一种协作式多任务切换的方式让正在运行的 goroutine 暂时停下来让其他等待执行的 goroutine 有机会运行。
package mainimport (fmtruntime
)func main() {go func(s string) {for i : 0; i 5; i {fmt.Println(s)}}(world)// 主程for i : 0; i 2; i {//切换 再次分配任务runtime.Gosched()fmt.Println(hello)}
}
2runtime.Goexit() 退出当时协程
package mainimport (fmtruntime
)func main() {go func() {defer fmt.Println(A.defer)func() {defer fmt.Println(B.defer)// 结束协程runtime.Goexit()defer fmt.Println(C.defer)fmt.Println(B)}()fmt.Println(A)}()for {}
}3 runtime.GOMAXPROCS Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上调度器会把Go代码同时调度到8个OS线程上GOMAXPROCS是m:n调度中的n。 Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。 Go1.5版本之前默认使用的是单核心执行。Go1.5版本之后默认使用全部的CPU逻辑核心数。
4将任务分配到不同的CPU逻辑核心上实现并行
单核心
package mainimport (fmtruntimetime
)func a() {for i : 1; i 10; i {fmt.Println(A:, i)}
}func b() {for i : 1; i 10; i {fmt.Println(B:, i)}
}func main() {runtime.GOMAXPROCS(1)go a()go b()time.Sleep(time.Second)
}输出看出是执行完一个goroutine 再执行另一个
B: 1
B: 2
B: 3
B: 4
B: 5
B: 6
B: 7
B: 8
B: 9
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9多核心
package mainimport (fmtruntimetime
)func a() {for i : 1; i 10; i {fmt.Println(A:, i)}
}func b() {for i : 1; i 10; i {fmt.Println(B:, i)}
}func main() {runtime.GOMAXPROCS(2)go a()go b()time.Sleep(time.Second)
}输出看出是并发执行
B: 1
B: 2
B: 3
B: 4
B: 5
B: 6
B: 7
B: 8
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9
B: 95Go语言中的操作系统线程和goroutine的关系
1.一个操作系统线程对应用户态多个goroutine。2.go程序可以同时使用多个操作系统线程。3.goroutine和OS线程是多对多的关系即m:n
四、channel
1CSP模型
并发执行函数的目的是让多个任务同时进行但仅仅并发执行函数是不够的因为这些函数可能需要相互交换数据。在并发环境中共享内存虽然可以用于数据交换但容易引发竞态问题而使用互斥量会影响性能。
Go语言采用了CSPCommunicating Sequential Processes并发模型强调通过通信来共享数据而不是通过共享数据来进行通信。这种方式更加安全且高效。 关键点 CSP模型 Go语言采用了CSP模型强调通过通信来实现协程goroutine间的数据交换而不是直接共享内存。 通道channel 通道是用于协程间通信的一种机制类似于一个队列保证了数据的顺序性。通过在通道中发送和接收数据协程可以安全地进行交互。 通道的特点 每个通道都有特定的元素类型通道的操作遵循先进先出原则。通过通道的发送和接收操作协程之间可以安全地进行数据交换避免了竞态问题。 并发优势 通过通道Go语言实现了安全且高效的并发编程允许协程在不同任务之间进行数据交换而不需要显式地使用互斥量进行加锁。
通过使用通道Go语言的并发模型强调了协程之间通过通信共享数据而不是通过共享数据来进行通信从而避免了许多传统并发模型中常见的问题。这使得并发编程更加安全、简洁和高效。
2channel 类型
channel 是一种类型引用类型
声明格式var 变量 chan 元素类型
例如var ch1 chan int // 声明一个传递整型的通道var ch2 chan bool // 声明一个传递布尔型的通道var ch3 chan []int // 声明一个传递int切片的通道3创建channel
通道是引用类型通道类型的空值是nil。var ch chan int
fmt.Println(ch) // nil声明的通道后需要使用make函数初始化之后才能使用。创建channel的格式如下make(chan 元素类型, [缓冲大小]) // 缓冲大小可选例如
ch4 : make(chan int)
ch5 : make(chan bool)
ch6 : make(chan []int)4channel 操作
1发送
ch - 10 // 把10发送到ch中2接收
x : - ch // 从ch中接收值并赋值给变量x
-ch // 从ch中接收值忽略结果3关闭 close(ch)只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的它和关闭文件是不一样的在结束操作之后关闭文件是必须要做的但关闭通道不是必须的。
注意 1.对一个关闭的通道再发送值就会导致panic。2.对一个关闭的通道进行接收会一直获取值直到通道为空。3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。4.关闭一个已经关闭的通道会导致panic。5无缓冲通道
func main() {ch : make(chan int)ch - 10fmt.Println(发送成功)
}//出现以下错误fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main().../src/github.com/pprof/studygo/day06/channel02/main.go:8 0x54无缓冲的通道必须有接收才能发送。上面的代码会阻塞在ch - 10这一行代码形成死锁
启动一个goroutine 解决该问题
func recv(c chan int) {ret : -cfmt.Println(接收成功, ret)
}
func main() {ch : make(chan int)go recv(ch) // 启用goroutine从通道接收值ch - 10fmt.Println(发送成功)
}无缓冲通道上的发送操作会阻塞直到另一个goroutine在该通道上执行接收操作这时值才能发送成功两个goroutine将继续执行。相反如果接收操作先执行接收方的goroutine将阻塞直到另一个goroutine在该通道上发送一个值。
使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此无缓冲通道也被称为同步通道。
6有缓冲通道
func main() {ch : make(chan int, 1) // 创建一个容量为1的有缓冲区通道ch - 10fmt.Println(发送成功)
}只要通道的容量大于零那么该通道就是有缓冲的通道通道的容量表示通道中能存放元素的数量。可以使用内置的len函数获取通道内元素的数量使用cap函数获取通道的容量。
7,从通道中遍历获取值
package mainimport fmtfunc main() {ch1 : make(chan int)ch2 : make(chan int)// 开启goroutine 将 0100 的数发到 ch1 中go func() {for i : 0; i 100; i {ch1 - i}close(ch1)}()// 开启goroutine 从ch1中接收值发送给ch2go func() {for {i, ok : -ch1if !ok {break}ch2 - i * i}close(ch2)}()// 在主goroutine 打印ch2for i : range ch2 {fmt.Println(ch2:, i)}
}
能从关闭通道中获取值通过遍历获取通道中关闭的通道也行的值
8单向通道
func counter(out chan- int) {for i : 0; i 100; i {out - i}close(out)
}func squarer(out chan- int, in -chan int) {for i : range in {out - i * i}close(out)
}
func printer(in -chan int) {for i : range in {fmt.Println(i)}
}func main() {ch1 : make(chan int)ch2 : make(chan int)go counter(ch1)go squarer(ch2, ch1)printer(ch2)
}1.chan- int是一个只能发送的通道可以发送但是不能接收2.-chan int是一个只能接收的通道可以接收但是不能发送。
8通道总结