并发:
是指一个时间段
中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
(同一时间段同时在做多个事情)
并行:
在操作系统中是指,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。
(同一时刻同时在做多个事情)
进程:
—个程序启动之后就创建了—个进程
线程:
操作系统调度的最小单元
协程:
协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源
goroutine:
go关键字为一个函数创建一个goroutine。一个函数可以被创建多个goroutine,一个 goroutine 必定对应一个函数
启动单个goroutine,只需在调用的函数(普通函数和匿名函数)前面加上一个go关键字
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("hello函数")
}
func main() {
//并发执行
go hello()
//休眠1s
time.Sleep(time.Second)
fmt.Println("main函数")
}
//输出结果如下
hello函数
main函数
defer语句
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("hello函数")
}
func main() {
defer fmt.Println("defer 语句") //main函数结束之前执行
//并发执行
go hello() //先创建一个goroutine;再goroutine中执行hello函数
fmt.Println("main函数")
//休眠1s
time.Sleep(time.Second)
}
//输出结果如下
main函数
hello函数
defer 语句
- 上面的代码中主线程为了等待goroutine都运行完毕,不得不在程序的末尾使用
time.Sleep()
来睡眠一段时间,等待其他线程充分运行。对于简单的代码,100个for循环可以在1秒之内运行完毕,time.Sleep()也可以达到想要的效果。- 但是对于实际生活的大多数场景来说,1秒是不够的,并且大部分时候我们都无法预知for循环内代码运行时间的长短。这时候就不能使用 time.Sleep() 来完成等待操作了。
Add()
, Done()
, Wait()
用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器地值减为0package main
import (
"fmt"
"sync"
)
//定义sync.WaitGroup结构体,内置计数器
var sw sync.WaitGroup
func hello() {
fmt.Println("hello 函数")
//执行完成通知,次数-1
sw.Done()
}
func test() {
fmt.Println("test 函数")
sw.Done()
}
func main() {
defer fmt.Println("defer 语句")
//执行前告知main打标记
//设置计数器次数
sw.Add(2)
go hello()
go test()
fmt.Println("main 函数")
//阻塞:一直等到所有goroutine执行结束
sw.Wait()
}
//输出结果如下
hello 函数
main 函数
test 函数
defer 语句
package main
import (
"fmt"
"sync"
)
//启用10个goroutine
var sw sync.WaitGroup
func hello(i int) {
fmt.Println("hello 函数", i)
sw.Done()
}
func main() {
sw.Add(10)
//创建10个goroutine
for i := 0; i < 10; i++ {
go hello(i)
}
fmt.Println("main 函数")
sw.Wait()
}
//输出结果如下
hello 函数 9
hello 函数 5
hello 函数 2
hello 函数 8
hello 函数 1
hello 函数 7
hello 函数 3
hello 函数 4
hello 函数 6
main 函数
hello 函数 0
panic
宕机前把错误信息发送到控制台上,程序结束,资源全部释放package main
import (
"fmt"
"sync"
)
//启用10个goroutine
var sw sync.WaitGroup
func hello(i int) {
fmt.Println("抢购商品流程", i)
if i == 6 {
panic("宕机报警")
}
sw.Done()
}
func main() {
sw.Add(10)
//创建10个goroutine
for i := 0; i < 10; i++ {
go hello(i)
}
sw.Wait()
}
//输出结果如下:
抢购商品流程 9
抢购商品流程 4
抢购商品流程 5
抢购商品流程 6
抢购商品流程 3
抢购商品流程 1
抢购商品流程 2
抢购商品流程 0
抢购商品流程 8
panic: 宕机报警
goroutine 12 [running]:
main.hello(0x6)
d:/goproject/src/dev_code/test04/main/main.go:15 +0xb7
created by main.main
d:/goproject/src/dev_code/test04/main/main.go:24 +0x57
exit status 2
可以看到panic影响到了整个main程序,触发后会释放掉内存
局部变量放在栈中,全局变量在堆中
可增长的栈
OS线程(操作系统线程)一般都有固定的栈内存(2MB),一个goroutine的栈在生命周期开始时只有很小的栈(2KB),goroutine的栈是不固定的,可以按需增加或者缩小,goroutine的栈大小限制可以达到1GB,虽然这种情况不多见,所以一次可以创建十万左右的goroutine是没问题的。
goroutine调度
OS线程由OS内核来调度,goroutine则是由Go运行时(runtime) 自己的调度器来调度,这个调度器使用一个m:n
调度的技术(复用/调度m个goroutine到n个OS线程),goroutine的调度不需要切换内核语境,所以调用一个goroutine 比调用一个线程的成本要低很多。
GOMAXPROCS
Go运行时的调度使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码,默认值是机器上的CPU核心数。例如:在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n
调度中的n) 。
Go可以通过runtime.GOMAXPROCS()
函数设置当前程序并发时占用的CPU逻辑核心数。
(Go1.5版本前默认是单核心执行,Go1.5版本后默认使用全部逻辑核心数)
示例:
通过将任务分配到不同的CPU逻辑核心上实现并行的效果
package main
import (
"fmt"
"runtime"
"sync"
)
//计数器结构体
var sw sync.WaitGroup
func a() {
defer sw.Done()
for i := 0; i < 20; i++ {
fmt.Println("A:", i)
}
}
func b() {
defer sw.Done()
for i := 0; i < 20; i++ {
fmt.Println("B:", i)
}
}
func main() {
//资源分配1个逻辑核心数
runtime.GOMAXPROCS(1)
sw.Add(2)
go a()
go b()
sw.Wait()
}
//输出结果如下
B: 0
B: 1
B: 2
B: 3
B: 4
B: 5
B: 6
B: 7
B: 8
B: 9
B: 10
B: 11
B: 12
B: 13
B: 14
B: 15
B: 16
B: 17
B: 18
B: 19
A: 0
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9
A: 10
A: 11
A: 12
A: 13
A: 14
A: 15
A: 16
A: 17
A: 18
A: 19
通信共享内存
而不是通过共享内存而实现通信语法:
var 变量 chan 元素类型
var ch1 chan int //传递整形的通道
var ch2 chan bool //传递布尔型的通道
var ch3 chan []int //传递整形切片的通道
package main
import "fmt"
//channel定义
func main() {
//channel是引用类型
var ch1 chan int //通道ch1传输int元素数据
var ch2 chan bool //通道ch2传输bool元素数据
fmt.Println("ch1: ", ch1)
fmt.Println("ch2: ", ch2)
}
//初始结果如下
ch1: <nil>
ch2: <nil>
package main
import "fmt"
//channel定义
func main() {
//使用make初始化channel
ch3 := make(chan int, 2)
//发送
ch3 <- 10
ch3 <- 20
//接收
result := <-ch3
result2 := <-ch3
fmt.Println("ch3: ", result, result2)
}
//输出结果如下
ch3: 10 20
package main
import "fmt"
//channel定义
func main() {
//使用make初始化channel
ch3 := make(chan int, 2)
//发送
ch3 <- 10
ch3 <- 20
//接收
result := <-ch3
result2 := <-ch3
fmt.Println("ch3: ", result, result2)
//关闭通道
close(ch3)
result = <-ch3
fmt.Println("ch3关闭后接收的值: ", result)
}
//输出结果如下
ch3: 10 20
ch3关闭后接收的值: 0
package main
import "fmt"
func main() {
var ch = make(chan int, 10)
for i := 0; i < 10; i++ {
ch <- i
if i == 6 {
close(ch)
break
}
}
//通道元素数量
leng := len(ch)
fmt.Printf("ch元素格式 %d, 容量 %d\n", leng, cap(ch)) //cap()取容量
for i := 0; i < leng; i++ {
result := <-ch
fmt.Println(result)
}
//数据量不确定的情况下取数据使用for range
/* for result := range ch {
fmt.Println(result)
} */
}
//输出结果如下
ch元素格式 7, 容量 10
0
1
2
3
4
5
6
package main
import "fmt"
//通道取值
func Result(ch chan bool) {
//取值,未取到会处于阻塞状态
ret := <-ch
fmt.Println(ret)
}
func main() {
var ch = make(chan bool)
go Result(ch)
//传递数据,同步执行
ch <- true
fmt.Println("main 函数")
}
//输出结果如下
true
main 函数
package main
import (
"fmt"
"time"
)
//通道取值
func Result(ch chan bool) {
ret := <-ch
fmt.Println(ret)
}
func main() {
//缓冲通道
var ch = make(chan bool, 10)
ch <- false
//获取数据量
fmt.Println(len(ch), cap(ch))
go Result(ch)
ch <- true
time.Sleep(time.Second)
fmt.Println("main 函数结束")
}
//输出结果如下
1 10
false
main 函数结束
package main
import "fmt"
//产生数据输入通道,输入完立即关闭
func Send(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func main() {
var ch = make(chan int, 100)
go Send(ch)
//从通道中取值
for {
result, ok := <-ch
if !ok { //值取完进行退出
break
}
fmt.Println(result)
}
/*for result := range ch {
fmt.Println(result)
}*/
}
//输出结果如下
0
1
2
3
4
5
6
7
8
9
生产者——》产生随机数
消费者——》计算每个随机数的每个位的数字的和
package main
import (
"fmt"
"math/rand"
"time"
)
//随机数通道
var itemChan chan *item
//随机数结构体
type item struct {
id int64
num int64
}
//求和通道
var resultChan chan *result
//求和结构体
type result struct {
item *item
sum int64
}
//生产者
func producer(ch chan *item) {
//生成随机数
var id int64
for {
id++
number := rand.Int63() //随机正整数
//构造结构体
tmp := &item{
id: id,
num: number,
}
//随机数发送到通道中
ch <- tmp
time.Sleep(time.Millisecond * 100)
}
}
//计算求和
func calc(i int64) int64 {
//求和变量
var sum int64
for i > 0 {
//得到每一个位数进行累加
sum = sum + i%10
i = i / 10
}
return sum
}
//消费者
func consumer(ch chan *item, resultChan chan *result) {
for {
tmp := <-ch
//数据运算
sum := calc(tmp.num) //tmp.num 就是(*tmp).name
//构造result结构体
resObj := &result{
item: tmp,
sum: sum,
}
//结果传递给通道result等待进行输出
resultChan <- resObj
}
}
//打印结果
func printResult(resultChan chan *result) {
for ret := range resultChan {
fmt.Printf("id:%v,num:%v,sum:%v\n", ret.item.id, ret.item.num, ret.sum)
time.Sleep(time.Second)
}
}
//指定数量的goroutine
func startWorker(n int, ch chan *item, resultChan chan *result) {
for i := 0; i < n; i++ {
go consumer(ch, resultChan)
}
}
func main() {
//通道初始化,结构体指针类型
itemChan = make(chan *item, 100)
resultChan = make(chan *result, 100)
//启用生产者goroutine
go producer(itemChan)
//消费者gotoutine
startWorker(30, itemChan, resultChan)
printResult(resultChan)
}
//输出结果如下
id:1,num:5577006791947779410,sum:95
id:2,num:8674665223082153551,sum:79
id:3,num:6129484611666145821,sum:81
id:4,num:4037200794235010051,sum:53
id:5,num:3916589616287113937,sum:95
id:6,num:6334824724549167320,sum:80
id:7,num:605394647632969758,sum:99
id:8,num:1443635317331776148,sum:77
id:9,num:894385949183117216,sum:89
id:10,num:2775422040480279449,sum:80
id:11,num:4751997750760398084,sum:99
...
...
...
package main
import (
"fmt"
"math/rand"
"sync"
)
//计数器处理消费者goroutine
var sw sync.WaitGroup
//随机数通道
var itemChan chan *item
//随机数结构体
type item struct {
id int64
num int64
}
//求和通道
var resultChan chan *result
//求和结构体
type result struct {
item *item
sum int64
}
//生产者
func producer(itemCh chan *item) {
//生成随机数
var id int64
for i := 0; i < 10000; i++ {
id++
number := rand.Int63() //随机正整数
//构造结构体
tmp := &item{
id: id,
num: number,
}
//随机数发送到通道中
itemCh <- tmp
}
//生产完成关闭itemChan通道
close(itemCh)
}
//计算求和
func calc(i int64) int64 {
//求和变量
var sum int64
for i > 0 {
//得到每一个位数进行累加
sum = sum + i%10
i = i / 10
}
return sum
}
//消费者
func consumer(itemch chan *item, resultChan chan *result) {
defer sw.Done()
for tmp := range itemch {
//数据运算
sum := calc(tmp.num) //tmp.num 就是(*tmp).name
//构造result结构体
resObj := &result{
item: tmp,
sum: sum,
}
//结果传递给通道result等待进行输出
resultChan <- resObj
}
}
//打印结果
func printResult(resultChan chan *result) {
for ret := range resultChan {
fmt.Printf("id:%v,num:%v,sum:%v\n", ret.item.id, ret.item.num, ret.sum)
}
}
//指定数量的goroutine
func startWorker(n int, ch chan *item, resultChan chan *result) {
for i := 0; i < n; i++ {
go consumer(ch, resultChan)
}
}
func main() {
//通道初始化,结构体指针类型
itemChan = make(chan *item, 10000)
resultChan = make(chan *result, 10000)
//启用生产者goroutine
go producer(itemChan)
//启用计数器
sw.Add(30)
//消费者gotoutine
startWorker(30, itemChan, resultChan)
//等待goroutine结束
sw.Wait()
//关闭resultChan通道
close(resultChan)
printResult(resultChan)
}
//输出结果如下
id:1,num:5577006791947779410,sum:95
id:2,num:8674665223082153551,sum:79
id:3,num:6129484611666145821,sum:81
id:4,num:4037200794235010051,sum:53
...
id:9997,num:661484091736918950,sum:87
id:9998,num:5665788214883279410,sum:94
id:9999,num:3873652276866279948,sum:108
id:10000,num:8455728612988973956,sum:112
package main
import (
"fmt"
"math/rand"
)
//随机数通道
var itemChan chan *item
//求和管道
var resultChan chan *result
//空结构体通道
var doneChan chan struct{}
//随机数结构体
type item struct {
id int64
num int64
}
//求和结构体
type result struct {
item *item
sum int64
}
//生产者
func producer(itemCh chan *item) {
var id int64
//指定数量数据
for i := 0; i < 10000; i++ {
//序列号自增
id++
number := rand.Int63() //随机正整数
//随机数结构体封装
tmp := &item{
id: id,
num: number,
}
//随机数放入通道
itemCh <- tmp
// time.Sleep(time.Millisecond*100)
}
//itemCh通道关闭
close(itemCh)
}
//求和运算
func calc(i int64) int64 {
//求和变量
var sum int64
for i > 0 {
//每位数字求和
sum = sum + i%10
i = i / 10
}
return sum
}
//消费者
func consumer(itemCh chan *item, resultCh chan *result) {
for tmp := range itemCh {
//数据运算
sum := calc(tmp.num)
//求和结构体封装
result := &result{
item: tmp,
sum: sum,
}
//求和数放入resultChan通道
resultCh <- result
}
//传递空结构体进通道
doneChan <- struct{}{}
}
//输出遍历
func printResult(resultChan chan *result) {
for ret := range resultChan {
fmt.Printf("id:%v;num:%v;sum:%v\n", ret.item.id, ret.item.num, ret.sum)
//节奏输出控制
// time.Sleep(time.Second)
}
}
//消费者goroutine数量控制
func startWorker(n int, ch chan *item, resultCh chan *result) {
//开启n数量的goroutine
for i := 0; i < n; i++ {
//goroutine调用函数
go consumer(ch, resultCh)
}
}
//监控goroutine即时关闭通道
func closeChan(n int, doneChan chan struct{}, resultChan chan *result) {
//监控goroutine次数
for i := 0; i < n; i++ {
<-doneChan
}
//计数通道关闭
close(doneChan)
//求和通道关闭
close(resultChan)
}
func main() {
//通道初始化,结构体指针类型
itemChan = make(chan *item, 10000)
resultChan = make(chan *result, 10000)
doneChan = make(chan struct{}, 30)
//启用生产者goroutine
go producer(itemChan)
//消费者goroutine,高并发处理
startWorker(30, itemChan, resultChan)
//启用goroutine监控流程是否结束
go closeChan(30, doneChan, resultChan)
//调用输出函数
printResult(resultChan)
}
语法示例:
select {
case communication clause:
statement(s);
case communication clause:
statement(s);
/* 你可以定义任何数量的case*/
default : /* 可选 */’
statement(s);
}
每个case 都必须是一个通信
所有channel表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行,其他被忽略。
示例:
package main
import (
"fmt"
"math"
"time"
)
//select语句
var ch1 = make(chan string, 100)
var ch2 = make(chan string, 100)
//发送数据k1
func sendK1(ch chan string) {
//产生数据
for i := 0; i < math.MaxInt64; i++ {
//像ch中发送数据
ch <- fmt.Sprintf("k1:%d\n", i)
//50毫秒放入一个数据
time.Sleep(time.Millisecond * 50)
}
}
//发送数据k2
func sendK2(ch chan string) {
//产生数据
for i := 0; i < math.MaxInt64; i++ {
//像ch中发送数据
ch <- fmt.Sprintf("k2:%d\n", i)
//100毫秒放入一个数据
time.Sleep(time.Millisecond * 100)
}
}
func main() {
go sendK1(ch1)
go sendK2(ch2)
//取值
for {
//如果通道都可通信,是随机公平执行其中一条,忽略其他;
//如果通道都不可通信,且没有default语句时候,处于阻塞状态,直到可以通信为止
select {
case ret := <-ch1:
fmt.Println(ret)
case ret := <-ch2:
fmt.Println(ret)
default:
fmt.Println("没有数据可取")
time.Sleep(time.Millisecond * 500)
}
}
}
//输出结果如下
没有数据可取
k2:0
k2:1
k2:2
k1:0
k1:1
k2:3
k2:4
k1:2
...
package main
import "fmt"
//select case通信原理
func main() {
var ch = make(chan int, 1)
for i := 0; i < 10; i++ {
//解决死锁问题
select {
case ch <- i:
case ret := <-ch:
fmt.Println(ret)
}
}
}
//输出结果如下
0
2
4
6
8
package main
import (
"fmt"
"math/rand"
"os"
"time"
)
// 传送随机数通道(使用指针传递)
var itemChan chan *item
// 传送求和值通道
var resultChan chan *result
// 空结构体传输通道
var doneChan chan struct{}
//随机数字结构体
type item struct {
id int64
num int64
}
//求和结构体
type result struct {
item *item
sum int64
}
//生产者函数
func producer(ch chan *item) {
// 1.生成随机数
var id int64
for {
// 序列号自增
id++
number := rand.Int63() // 随机生成正整数
// 随机数结构体封装
tmp := &item{
id: id,
num: number,
}
// 2. 随机数发送到通道中
ch <- tmp
}
}
//计算求和函数
func calc(num int64) int64 {
// 和:值
var sum int64
for num > 0 {
// 得到每一个位数进行累加
sum = sum + num%10
num = num / 10
}
return sum
}
//消费者函数
func consumer(ch chan *item, resultChan chan *result) {
// 从 itemChan 通道中取随机数结构体指针
for tmp := range ch {
sum := calc(tmp.num) // tmp.num 就是 (*tmp).num,会自动识别指针
// 构造 result 结构体
resObj := &result{
item: tmp,
sum: sum,
}
// 结果传递通道 resultChan 等待进行输出
resultChan <- resObj
}
}
// 打印结果
func printResult(doneChan chan struct{}, resultChan chan *result) {
for {
// 等待 doneChan 输入退出输出信息
select {
case ret := <-resultChan:
fmt.Printf("id:%v,num:%v,sum:%v\n", ret.item.id, ret.item.num, ret.sum)
time.Sleep(time.Second)
case <-doneChan:
return
}
}
}
// 监听键盘输入字符传递给 doneChan 通道
func inputChan(doneChan chan struct{}) {
// 一个字符的输入
tmp := [1]byte{}
// 从标准输入获取值,未输入一直处于等待状态
os.Stdin.Read(tmp[:])
doneChan <- struct{}{}
}
// 灵活启用指定数量的 goroutine
// n 为要开启的 goroutine 数量
func startWorker(n int, ch chan *item, resultChan chan *result) {
for i := 0; i < n; i++ {
go consumer(ch, resultChan)
}
}
func main() {
// 通道初始化,结构体指针类型
itemChan = make(chan *item, 100)
resultChan = make(chan *result, 100)
doneChan = make(chan struct{}, 1)
// 启用生产者 goroutine
go producer(itemChan)
// 消费者 goroutine,高并发处理
startWorker(30, itemChan, resultChan)
go inputChan(doneChan)
// 打印结果
printResult(doneChan, resultChan)
}
在函数中稚嫩那个发送值而不能接收值为只写通道
只能接收不能发送值称为只读通道
可以让代码意向更明确,更清晰
示例:
package main
import "fmt"
var ch chan int
//只写通道,针对一个函数中实现
func writeCh(ch chan<- int) {
ch <- 10
}
//只读通道
func read(ch <-chan int) int {
ret := <-ch
return ret
}
func main() {
ch = make(chan int, 1)
writeCh(ch)
fmt.Println(read(ch))
}
//输出结果如下
10
出现panic情况:
通道关闭以后,可以取值,但是不能传递值,会出现panic
通道关闭后,如果再次关闭通道,会panic
出现死锁的情况:
通道中元素的容量超出也会死锁
没有传递元素到通道里,直接取值,会死锁