Go编程语言:支持并发、垃圾回收的编译型系统级编程语言!Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据。
一、并发concurrency
1.基本概念
Go能处理高并发的根本原因在于执行go协程只需极少的栈内存(大概4~5KB),并且能根据需要动态增长和缩减占用的资源。
Go的高并发其实是由goroutine实现的,goroutine是由官方实现的超级“线程池”。
简单而言,goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行,而是通过系统的线程来多路派遣这些函数的执行,使得每个用go
关键字执行的函数可以运行成为一个单位协程。当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。而且调度的开销非常小,一颗CPU调度的规模不下于每秒百万次,这使得在程序中能够创建大量的goroutine,实现高并发的同时,依旧能保持高性能。
Goroutine 奉行通过通信来共享内存,而不是共享内存来通信。
2.区别并发和并行
这里涉及到一个问题,很多同学搞不清楚并发与并行的区别,这里我根据我根据知乎上这个问题某位网友的例子,我觉得很好:
- 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行
- 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发
- 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行
并发:你有处理多个任务的能力,不一定同时(一个CPU轮流)
并行:有同时处理多个任务的能力(多个CPU同时)
并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)CPU执行,可以说明是并行,并发是多个线程被一个CPU轮流切换着执行。
并发不是并行:Concurrency Is Not Parallelism
并发主要由切换时间片来实现“同时”运行,而并行则是直接利用多核实现多线程的运行,但Go可以设置使用核数,以发挥多核计算机的能力。
并发时,由于cpu执行的太快了,不停地来回切换,让人以为是同时进行。
并发比并行更优秀,充分的利用了CPU。
3.信道Channel
-
Channel
是goroutine
沟通的桥梁,大都是阻塞同步的 - 通过
make
创建,close
关闭 - Channel 是引用类型
- 可以使用
for range
来迭代不断操作channel - 可以设置单向或双向通道
- 可以设置缓存大小,在未被填满前不会发生阻塞
4.Select
- 可处理一个或多个channel的发送与接收
- 同时有多个可用的channel时按随机排序处理
- 可用空的 select 来阻塞 main 函数
- 可设置超时
二、并发示例
1.未使用channel关键字
package main
import (
"fmt"
"time"
)
func main() {
go Go() // 使用go关键字就可执行goroutine
time.Sleep(2 * time.Second)
fmt.Println("Hey ...!")
}
func Go() {
fmt.Println("Go ...!")
}
打印:
➜ src go run myfirstgo/concurrency.go
Go ...!
Hey ...!
2.使用channel关键字
package main
import (
"fmt"
)
func main() {
// 创建channel
c := make(chan bool)
// 执行到此处会阻塞
go func() {
fmt.Println("Go ...!")
c <- true
}()
// 将true的东西存到channel当中,然后读出来结束运行,即通知main函数,我这里运行完成了,可以结束了
<-c
fmt.Println("Hey ...!")
}
打印:
➜ src go run myfirstgo/concurrency.go
Go ...!
Hey ...!
3.range示例
func main() {
// 创建channel
c := make(chan bool)
// 执行到此处会阻塞
go func() {
fmt.Println("Go ...!")
c <- true
close(c)
}()
for v := range c {
fmt.Println(v)
}
fmt.Println("Hey ...!")
}
打印:
➜ src go run myfirstgo/concurrency.go
Go ...!
true
Hey ...!
4.多核
package main
import (
"fmt"
"runtime"
)
// 当使用单线程执行时,会按部就班,按照顺序1,2,3,4执行下去
// 当使用多个CPU核数时,任务分配是不定的,
func main() {
// 使用多核
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool, 10)
for i := 0; i < 10; i++ {
go Go(c, i)
}
<-c
}
func Go(c chan bool, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
if index == 9 {
c <- true
}
}
运行多次的执行结果:
➜ src go run myfirstgo/concurrency.go
3 4999999950000001
0 4999999950000001
1 4999999950000001
9 4999999950000001
➜ src go run myfirstgo/concurrency.go
2 4999999950000001
1 4999999950000001
3 4999999950000001
9 4999999950000001
➜ src go run myfirstgo/concurrency.go
9 4999999950000001
➜ src go run myfirstgo/concurrency.go
2 4999999950000001
9 4999999950000001
当使用多个CPU核数(runtime.GOMAXPROCS)时,任务分配是不定的,所以会出现上边的结果。
这里有两种解决方案:
第一种:
设置一个缓存长度为10的channel
package main
import (
"fmt"
"runtime"
)
// 当使用单线程执行时,会按部就班,按照顺序1,2,3,4执行下去
// 当使用多个CPU核数时,任务分配是不定的,
func main() {
// 使用多核
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool, 10)
for i := 0; i < 10; i++ {
go Go(c, i)
}
// 设置一个缓存长度为10 的channel
for i := 0; i < 10; i++ {
<-c
}
}
func Go(c chan bool, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
c <- true
}
打印:
➜ src go run myfirstgo/concurrency.go
1 4999999950000001
2 4999999950000001
9 4999999950000001
3 4999999950000001
7 4999999950000001
0 4999999950000001
8 4999999950000001
6 4999999950000001
4 4999999950000001
5 4999999950000001
➜ src go run myfirstgo/concurrency.go
0 4999999950000001
9 4999999950000001
5 4999999950000001
1 4999999950000001
7 4999999950000001
2 4999999950000001
8 4999999950000001
3 4999999950000001
6 4999999950000001
4 4999999950000001
第二种:
不是通过channel解决的,而是通过sync
包解决的,它有一个waitGroup
,可以创建一个任务组,可以添加任务,每完成一个任务,就标记完成Done
,main函数的主要作用就是判断是否所有的任务都完成了,如果都完成了,则退出程序。
package main
import (
"fmt"
"runtime"
"sync"
)
// 当使用单线程执行时,会按部就班,按照顺序1,2,3,4执行下去
// 当使用多个CPU核数时,任务分配是不定的,
func main() {
// 使用多核
runtime.GOMAXPROCS(runtime.NumCPU())
// sync
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go Go(&wg, i)
}
wg.Wait()
}
func Go(wg *sync.WaitGroup, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
wg.Done()
}
打印:
➜ src go run myfirstgo/concurrency.go
1 4999999950000001
5 4999999950000001
7 4999999950000001
9 4999999950000001
6 4999999950000001
8 4999999950000001
0 4999999950000001
2 4999999950000001
3 4999999950000001
4 4999999950000001
➜ src go run myfirstgo/concurrency.go
1 4999999950000001
9 4999999950000001
2 4999999950000001
3 4999999950000001
4 4999999950000001
5 4999999950000001
0 4999999950000001
6 4999999950000001
7 4999999950000001
8 4999999950000001
相关文章:
golang语言并发与并行——goroutine和channel的详细理解(一)
golang的goroutine调度机制
有疑问加站长微信联系(非本文作者)