并发指在同一时间内可以执行多个任务。并发编程含义比较广泛,包含多线程编程、多进程编程及分布式程序等。
Go语言里的并发指的是能让某个函数独立于其他函数运行的能力。当一个函数创建为协程(goroutine)时,Go语言会将其视为一个独立的工作单元,这个单元会被调度到可用的逻辑处理器上执行。
并发编程基础
1、并发与并行(理论没看懂)
理解操作系统的线程(thread)和进程(process),有助于理解Go语言运行时调度器如何得用操作系统来并发运行goroutine。
并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。
使用较少的资源做更多的事情,是指导Go语言设计的哲学。
2、指定使用核心数
Go语言默认会调用CPU核心数,这些功能都是自动调整的,但是也提供了相应的标准库来指定核心数。使用flags包可以调整程序运行时调用的CPU核心数。
代码如下:
package main
import (
"fmt"
"time"
)
func longWait() {
fmt.Println("开始longWait()")
time.Sleep(5 * 1e9)
fmt.Println("结束longWait()")
}
func shortWait() {
fmt.Println("开始shortWait()")
time.Sleep(2 * 1e9)
fmt.Println("结束shortWait()")
}
func main() {
fmt.Println("这里是main()开始的地方")
go longWait()
go shortWait()
fmt.Println("挂起main()")
time.Sleep(10 * 1e9)
fmt.Println("这里是main()结束的地方")
}
main()、longWait()和shortWait()三个函数作为独立的处理单元按顺序启动,然后开始并行运行,每一个函数都在运行的开始和结束阶段输出了消息。
运行结果如下:
这里是main()开始的地方
挂起main()
开始longWait()
开始shortWait()
结束shortWait()
结束longWait()
这里是main()结束的地方
如果移除go语言关键字同,重新运行程序:
这里是main()开始的地方
开始longWait()
结束longWait()
开始shortWait()
结束shortWait()
挂起main()
这里是main()结束的地方
协程(goroutine)
操作系统自己掌管的进程(process)、进程内的线程(thread)以及进程内的协程(coroutine,也叫轻量级线程)。
协程的最大优势在于其“轻量级”,可以轻松创建上百万个协程而不会导致系统资源衰竭,而线程和进程通常最多也不能超过1万个。
Go语言在语言级别支持轻量级线程,叫goroutine。
1、协程基础
goroutine是Go语言并行设计的核心。
goroutine是通过Go程序的runtime管理的一个线程管理器。goroutine通过go关键字实现了,其实就是一个普通的函数。
代码如下:
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched()
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
运行结果如下:与书本不一样。
hello
hello
hello
hello
hello
书本是:
hello
world
hello
world
hello
world
hello
world
hello
并发处理:Go例程,代码如下:
package main
import (
"fmt"
)
func routine1(index int) {
for i := 0; i < 10; i++ {
fmt.Println(index, " : ", i)
}
}
func main() {
go routine1(1)
go routine1(2)
for i := 0; i < 10; i++ {
fmt.Println("0: ", i)
}
}
运行结果如下:(感觉不对,不明白什么意思)
0: 0
0: 1
0: 2
0: 3
0: 4
0: 5
0: 6
0: 7
0: 8
0: 9
2、协程间通信
通常有两种最常见的并发通信模型:共享数据和消息。
代码如下:
package main
import (
"fmt"
"runtime"
"sync"
)
var counter int = 0
func Count(lock *sync.Mutex) {
lock.Lock()
counter++
fmt.Println(z)
lock.Unlock()
}
func main() {
lock := &sync.Mutex{}
for i := 0; i < 10; i++ {
go Count(lock)
}
for {
lock.Lock()
c := counter
lock.Unlock()
runtime.Gosched()
if c >= 10 {
break
}
}
}
运行结果如下:
# command-line-arguments
./main.go:14:14: undefined: z
不要通过共享内存来通信,而应该通过通信来共享内存。
package main
import "fmt"
func Add(x, y int) {
z := x + y
fmt.Println(z)
}
func main() {
for i := 0; i < 10; i++ {
go Add(i, i)
}
}
无运行结果。
通道(Channel)
代码如下:
package main
import (
"fmt"
)
func Count(ch chan int) {
ch <- 1
fmt.Println("Counting")
}
func main() {
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Count(chs[i])
}
for _, ch := range chs {
<-ch
}
}
1、基本语法
一般channel的声明形式为:
var chanName chan ElementType
在channel的用法中,最常见的包括写入和读出。
将一个数据写入(发送)到channel的语法很直观:
ch <- value
从channel中读取数据的语法是:
value:=<-ch
2、select
Go语言直接在语言级别支持select关键字,用于处理异步I/O问题。
3、缓冲机制
4、超时和计时器
5、channel的传递
6、单向channel
7、关闭channel
使用go语言内置的close()函数如下:
close(ch)
并发进阶
1、多核并行化
2、协程同步
只有在当需要告诉接受者不会再提供新的值的时候,才需要关闭通道。
只有发送者需要关闭通道,接收都永远不会需要。
代码如下:
package main
import (
"fmt"
"time"
)
func sendData(ch chan string) {
ch <- "纽约"
ch <- "华盛顿"
ch <- "伦敦"
ch <- "北京"
ch <- "东京"
}
func getData(ch chan string) {
var input string
for {
input = <-ch
fmt.Printf("%s ", input)
}
}
func main() {
ch := make(chan string)
go sendData(ch)
go getData(ch)
time.Sleep(1e9)
}
运行结果如下:
纽约 华盛顿 伦敦 北京 东京
代码如下:
package main
import (
"fmt"
)
func sendData(ch chan string) {
ch <- "纽约"
ch <- "华盛顿"
ch <- "伦敦"
ch <- "北京"
ch <- "东京"
close(ch)
}
func getData(ch chan string) {
for {
input, open := <-ch
if !open {
break
}
fmt.Printf("%s ", input)
}
}
func main() {
ch := make(chan string)
go sendData(ch)
getData(ch)
}
运行结果如下:
纽约 华盛顿 伦敦 北京 东京
为一个普通函数创建goroutine的写法如下:
go 函数名(参数列表)
函数名:要调用的函数名。
参数列表:调用函数需要传入的参数。
使用go关键字创建goroutine时,被调用函数的返回值会被忽略。
代码如下:
package main
import (
"fmt"
"time"
)
func running() {
var times int
//构建一个无限循环
for {
times++
fmt.Println("tick", times)
//延时1秒
time.Sleep(time.Second)
}
}
func main() {
//并发执行程序
go running()
//接受命令行输入,不做任何事情
var input string
fmt.Scanln(&input)
}
使用匿名函数创建goroutine的格式
go func (参数列表) {
函数体
} (调用参数列表)
参数列表:函数体内的参数变量列表
函数体:匿名函数的代码。
调用参数列表:启动goroutine时,需要向匿名函数传递的调用函数。
代码如下:
package main
import (
"fmt"
"time"
)
func main() {
go func() {
var times int
for {
times++
fmt.Println("tick", times)
time.Sleep(time.Second)
}
}()
var input string
fmt.Scanln(&input)
}
调整并发的运行恒通(GOMAXPROCS)
格式如下:
runtime.GOMAXPROCS(逻辑CPU数量)
这里的逻辑CPU数量可以有如下几种数值:
<1:不修改任何数值。
=1:单核心执行。
>1:多核并发执行。
一般情况下,可以使用funtime.NumCPU()查询CPU数量 ,并使用runtime.GOMAXPROCS()函数进行设置,例如:
runtime.GOMAXPROCS(runtime.NumCPU())
理解并发和并行
并发(concurrency):把任务在不同的时间点交给处理器进行处理。在同一个时间点,任务并不会同时运行。
并行(parallelism):把每一个任务分配给每一个处理器独立完成。在同一个时间点,任务一定是同时运行。
代码如下:
package main
import (
"fmt"
)
func sum(values []int, resultChan chan int) {
sum := 0
for _, value := range values {
sum += value
}
resultChan <- sum //将计算结果发送到channel中
}
func main() {
values := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
resultChan := make(chan int, 2)
go sum(values[:len(values)/2], resultChan)
go sum(values[len(values)/2:], resultChan)
sum1, sum2 := <-resultChan, <-resultChan //接收结果
fmt.Println("Result:", sum1, sum2, sum1+sum2)
}
运行结果如下:
Result: 40 15 55
去程:
代码如下:
package main
import (
"fmt"
"time"
)
func sheep(i int) {
for ; ; i += 2 {
fmt.Println(i, "只羊")
}
}
func main() {
go sheep(1)
go sheep(2)
time.Sleep(time.Millisecond)
}
运行结果如下:
2 只羊
4 只羊
6 只羊
8 只羊
10 只羊
12 只羊
14 只羊
16 只羊
18 只羊
20 只羊
22 只羊
24 只羊
26 只羊
28 只羊
30 只羊
32 只羊
34 只羊
36 只羊
38 只羊
40 只羊
42 只羊
44 只羊
46 只羊
48 只羊
50 只羊
52 只羊
54 只羊
56 只羊
58 只羊
60 只羊
62 只羊
64 只羊
66 只羊
68 只羊
70 只羊
72 只羊
74 只羊
76 只羊
78 只羊
80 只羊
82 只羊
84 只羊
86 只羊
88 只羊
90 只羊
92 只羊
94 只羊
96 只羊
98 只羊
100 只羊
102 只羊
104 只羊
106 只羊
108 只羊
110 只羊
回程:
代码如下:
package main
import (
"fmt"
"time"
)
var wormhole chan time.Time
func deepspace() {
wormhole <- time.Now()
}
func main() {
wormhole = make(chan time.Time)
go deepspace()
fmt.Println(<-wormhole)
}
运行结果如下:
2022-04-06 15:47:02.3888929 +0800 CST m=+0.002617601
遍历与关闭:
代码如下:
package main
import (
"fmt"
"time"
)
type hole chan time.Time
func deepspace(w hole, h int) {
defer close(w)
for ; h > 0; h-- {
w <- time.Now()
time.Sleep(time.Second)
}
}
func consumer(w hole) {
for msg := range w {
fmt.Println("consumer", msg)
}
}
func main() {
w := make(hole)
go deepspace(w, 8)
go consumer(w)
for {
msg, ok := <-w
if !ok {
break
}
fmt.Println("main", msg)
}
fmt.Println("Done")
}
运行结果如下:
main 2022-04-09 14:52:07.041789 +0800 CST m=+0.002800701
consumer 2022-04-09 14:52:08.0456268 +0800 CST m=+1.006638501
main 2022-04-09 14:52:09.0578227 +0800 CST m=+2.018834401
consumer 2022-04-09 14:52:10.0705782 +0800 CST m=+3.031589901
main 2022-04-09 14:52:11.0742367 +0800 CST m=+4.035248401
consumer 2022-04-09 14:52:12.0782694 +0800 CST m=+5.039281101
main 2022-04-09 14:52:13.0930031 +0800 CST m=+6.054014801
consumer 2022-04-09 14:52:14.0936872 +0800 CST m=+7.054698901
Done
MapReduce:
映射化简:每个去程对应一个Map并发执行,结果写入缓冲程道,最后再用一个Reduce函数综合这个程道得到的每一个值。
代码如下:
package main
import (
"fmt"
"math/rand"
)
type hole chan int
func deepspace(w hole) {
w <- rand.Int()
}
func main() {
n := 8
w := make(hole, n)
//Map
for i := 0; i < n; i++ {
go deepspace(w)
}
//Reduce
t := 0
for i := 0; i < n; i++ {
t += <-w
}
fmt.Println("Total:", t)
}
运行结果 如下:
Total: -174686420686987236
两个goroutine进行并行的累加计算:代码如下:
package main
import (
"fmt"
)
func sum(values []int, resultChan chan int) {
sum := 0
for _, value := range values {
sum += value
}
resultChan <- sum
}
func main() {
values := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
resultChan := make(chan int, 2)
go sum(values[:len(values)/2], resultChan)
go sum(values[len(values)/2:], resultChan)
sum1, sum2 := <-resultChan, <-resultChan
fmt.Println("Result:", sum1, sum2, sum1+sum2)
}
运行结果如下:
Result: 40 15 55
select语句:
代码如下:
package main
import (
"fmt"
"math/rand"
"time"
)
type hole chan int
func deepspace(w hole, h int) {
defer close(w)
d := time.Duration(rand.Intn(h)) * time.Second
for ; h > 0; h-- {
w <- rand.Int()
time.Sleep(d)
}
}
func main() {
n := 8
w := make(hole)
t := 0
maxTime := time.Second
go deepspace(w, n)
Out:
for i := 0; i < n; i++ {
select {
case n := <-w:
t += n
case <-time.After(maxTime):
fmt.Println("Time out")
break Out
}
}
fmt.Println("Total:", t)
}
运行结果如下:
Time out
Total: 8674665223082153551
程道值:
代码如下:
package main
import (
"fmt"
)
type Read struct {
key string
reply chan<- string
}
type Write struct {
key string
val string
}
var hole = make(chan interface{})
func deepspace() {
m := map[string]string{}
for {
switch r := (<-hole).(type) {
case Read:
r.reply <- m[r.key] + " from Mars."
case Write:
m[r.key] = r.val
}
}
}
func main() {
go deepspace()
hole <- Write{"Name", "Martin"}
home := make(chan string)
hole <- Read{"Name", home}
fmt.Println(<-home)
}
运行结果如下:
Martin from Mars.