目录
基础概念
并发:是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。同一时间段同时在做多个事情。
并行:在操作系统中是指,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。同一时刻同时在做多个事情。
进程:一个程序启动之后就创建了—个进程。
线程:操作系统调度的最小单元。
协程:用户态的线程。
GoRoutine: go关键字为一个函数创建一个goroutine。一个函数可以被创建多个goroutine,一个goroutine必定对应一个函数。
示例:启动单个goroutine,只需在调用的函数(普通函数和匿名函数)前面加上一个go关键字。
package main
import "fmt"
func hello(){
fmt.Println("hello函数")
func main() {
//并发执行
go hello()
fmt. Printin("main函数")
}
运行结果
main函数
等待一秒钟
package main
import (
"fmt"
//"time"
)
func hello() {
fmt.Println("hello函数")
}
func main() {
go hello() //启用了一个goroutine
//等待一秒
//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() //:先创建一个goroutinej 2:再goroutine中执行hello函数。
fmt.Println("main函数")
time.Sleep(time.Second) //等待一秒主程序再结束
}
延迟执行defer语句
main函数
hello函数
defer语句
sync.WaitGroup
使用sync WaitGroup无需设置等待时间,它会自动等待所有goroutine执行完成后再结束main,效率提升。
package 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语句")
//设置计数器次数
sw.Add(2)
go hello()
go test()
fmt.Println("main函数")
//等待计数器归零,结束main函数,否则一直处于等待状态
sw.Wait()
}
运行结果
hello函数
main函数
test函数
defer语句
多goroutine执行
package main
import (
"fmt"
"sync"
)
var sw sync.WaitGroup
func hello(i int) {
fmt.Println("抢购商品", i)
sw.Done()
}
func main() {
sw.Add(10)
for i := 0; i < 10; i++ {
go hello(i)
}
//等待机制
sw.Wait()
}
运行结果
抢购商品 0
抢购商品 6
抢购商品 5
抢购商品 7
抢购商品 9
抢购商品 8
抢购商品 1
抢购商品 2
抢购商品 4
抢购商品 3
panic测试
示例:panic宕机前把错误信息发送到控制台上,程序结束,资源全部释放。
package main
import (
"fmt"
"sync"
)
//定义sync. waitGroup结构体,内置计数器
var sw sync.WaitGroup
func hello(i int) {
//defer sw. Done()消亡前计数-1 goroutine会全部执行完
fmt.Println("hello函数", i)
if i == 8 {
panic("宕机报警")
}
sw.Done() //遇到panic不执行
}
func main() {
defer fmt.Println("defer语句") //启用10个goroutine
sw.Add(10)
for i := 0; i < 10; i++ {
go hello(i)
}
sw.Wait()
}
运行结果
hello函数 0
hello函数 9
hello函数 5
hello函数 6
hello函数 7
hello函数 8
hello函数 2
hello函数 1
hello函数 3
panic: 宕机报警
goroutine 14 [running]:
main.hello(0x8)
d:/golang/src/day25/doue goroutine/demo1/main.go:17 +0xb7
created by main.main
d:/golang/src/day25/doue goroutine/demo1/main.go:30 +0xb0
exit status 2
Goroutine和线程
可增长的栈
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 < 10; i++ {
fmt.Println("A", i)
}
}
func b() {
defer sw.Done()
for i := 0; i < 20; i++ {
fmt.Println("B", i)
}
}
func main() {
//调度
runtime.GOMAXPROCS(2)
sw.Add(2)
go b()
go a()
//time.Sleep(time.Second)
sw.Wait()
}
运行结果
PS D:\golang\src\day25\Even> go run .\main.go
A 0
A 1
A 2
A 3
A 4
A 5
A 6
A 7
A 8
A 9
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 18
B 19
PS D:\golang\src\day25\Even> go run .\main.go
A 0
A 1
A 2
A 3
A 4
A 5
A 6
A 7
A 8
A 9
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 18
B 19
PS D:\golang\src\day25\Even> go run .\main.go
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 8
A 9
channel
单纯的将函数并发执行没有意义。函数与函数间需要交换数据才能体现并发执行函数的意义。
虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。
go语言的并发模型是SCP,提倡通过通信共享内存而不是通过共享内存而实现通信。
如果说goroutine是Go程序并发的执行体,channel就是它们之间连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
go语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,在声明channel的时候需要为其指定元素类型。
声明语法:
var变量chan 元素类型
示例
var ch1 chan int//传递整型的通道
var ch2 chan bool //传递布尔型的通道
var ch3 chan []int//传递整型切片的通道
初始值类型
package main
import "fmt"
//channel定义
func main() {
var ch1 chan int //通道ch1传输int元素数据
var ch2 chan string //通道ch2传输string元素数据
fmt.Println("ch1:", ch1)
fmt.Println("ch2:", ch2)
}
运行结果
ch1: <nil>
ch2: <nil>
示例添加值
package main
import "fmt"
//channel定义
func main() {
var ch1 chan int //通道ch1传输int元素数据
var ch2 chan string //通道ch2传输string元素数据
fmt.Println("ch1:", ch1)
fmt.Println("ch2:", ch2)
//make初始化channel
ch3 := make(chan int, 1)
//发送
ch3 <- 10
//接收
result := <-ch3
fmt.Println("ch3:", result)
}
运行结果
ch1: <nil>
ch2: <nil>
ch3: 10
package main
import "fmt"
//channel定义
func main() {
var ch1 chan int //通道ch1传输int元素数据
var ch2 chan string //通道ch2传输string元素数据
fmt.Println("ch1:", ch1)
fmt.Println("ch2:", ch2)
//make初始化channel
ch3 := make(chan int, 2)
//发送
ch3 <- 10
ch3 <- 20
//接收
result := <-ch3
result1 := <-ch3
fmt.Println("ch3:", result, result1)
//关闭ch3
close(ch3)
//关闭信道,是否能添加值,不被运行
//ch3 <-30
result = <-ch3
fmt.Println("关闭之后result的值:",result)
}
运行结果
ch1: <nil>
ch2: <nil>
ch3: 10 20
关闭之后result的值: 0
关闭信道,再取值,可以正常取值,再做改动都会崩溃报panic(添加值、再次关闭信道),
死锁:①:make空间为1,但你传入信道的值大于1会出现死锁
//make初始化channel ch3 := make(chan int, 1) //发送 ch3 <- 10 ch3 <- 20 result := <-ch3 fatal error : all goroutines are asleep - deadlock!
②:当channel没有值,取值的时候会出现死锁
示例
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("个数为%d,容量为:%d\n", leng, cap(ch)) //数据量不确定的情况下取数据 for result := range ch { fmt.Println(result) } // for i := 0; i < leng; i++ { // result := <-ch // fmt.Println(result) // } } 运行结果 个数为7,容量为:10 0 1 2 3 4 5 6
无缓存通道和缓存通道
示例,无缓存通道
package main
import (
"fmt"
"time"
)
//传递值
func Value(ch chan bool) {
//取值,未取到值会阻塞
rel := <-ch
fmt.Println(rel)
}
func main() {
//设置缓存区,异步执行
ch := make(chan bool)
//同步执行
go Value(ch)
ch <- true
fmt.Println("main函数")
}
运行结果
true
mian函数
缓存区通道
package main
import (
"fmt"
"time"
)
//传递值
func Value(ch chan bool) {
//取值,未取到值会阻塞
rel := <-ch
fmt.Println(rel)
}
func main() {
//设置缓存区,异步执行
ch := make(chan bool, 10)
ch <- false
fmt.Printf("个数:%d,容量:%d\n", len(ch), cap(ch))
//同步执行
go Value(ch)
ch <- true
time.Sleep(time.Second)
fmt.Println("main函数")
}
个数:1,容量:10
false
main函数
判断是否取完值
package main
import "fmt"
var ch1 chan int
func send(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func main() {
//初始化
ch1 = make(chan int, 100)
go send(ch1)
for ret := range ch1 {
fmt.Println(ret)
}
//将取到的值放入道chan当中
// for {
// ret, ok := <-ch1
// //判断是否取完值
// if !ok {
// break
// }
// fmt.Println(ret)
// }
}
运行结果
0
1
2
3
4
5
6
7
8
9