1.并发编程与协程Goroutine
1.1并行与并发
并行:在同一时刻,有多条指令在多个处理器上同时执行,借助多核cpu实现
并发:宏观:用户体验上,程序在并行执行
微观:多个计划任务,顺序执行。在飞快的切换,轮换使用cpu时间轮片
时间轮片:

1.2常见并发编程技术
进程并发:
程序:编译成功得到的二进制文件。 (占用磁盘空间) (死的) 1
进程:运行起来的程序,占用系统资源。 (内存) (活的) N
进程状态:初始态、就绪态、运行态、挂起(阻塞态)、终止(停止态)

线程并发:
线程:LWP 轻量级的进程 最小的执行单位--cpu分配时间轮片的对象
进程:最小的系统资源分配单位,给线程提供执行环境
线程同步:指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一 致性,不能调用该功能。
同步:协同步调,规划先后顺序
线程同步机制:互斥锁(互斥量):建议锁,拿到锁以后,才能访问数据,没有拿到锁的线程,阻塞等待
读写锁:一把锁(读属性、写属性),写独占,读共享,写锁优先级高
协程并发:
协程:coroutine,轻量级线程,提高程序执行的效率
总结:进程(稳定性强)、线程(节省资源)、协程(效率高)都可以完成并发。
老板--手机
生产线--设备、材料、厂房--进程(资源分配单位)
工人--线程 --单进程、单线程的程序
50工人--50线程 --单进程、多线程的程序
10条生产线--500工人 --多进程、多线程的程序
利用闲暇时间义务板砖 --协程
1.3Goroutine
1.3.1Goroutine概念,创建及特性
概念:go程,创建于进程中,直接使用go关键,放置于函数调用前面,产生一个go程,实现并发
package main
import (
"fmt"
"time"
)
func sing() {
for i := 0; i < 5; i++ {
fmt.Println("唱--爱是一本书")
time.Sleep(100 * time.Millisecond)
}
}
func dance() {
for i := 0; i < 5; i++ {
fmt.Println("跳--跳一跳")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go sing()
go dance()
for {
;
}
}

实例:
package main
import (
"fmt"
"time"
)
func newTask() {
i := 0
for {
i++
fmt.Printf("new goroutine:i=%d\n", i)
time.Sleep(1 * time.Second) //延时1s
}
}
func main() {
//创建一个goroutine,启动另外一个任务
go newTask()
i := 0
//main goroutine 循环打印
for {
i++
fmt.Printf("main goroutine:i=%d\n", i)
time.Sleep(1 * time.Second) //延时1s
}
}
特性:主go程结束,子go程随之退出。
package main
import (
"fmt"
"time"
)
func main() {
//创建一个子go程
go func() {
for i := 0; i < 10; i++ {
fmt.Println("-----I'm goroutine-----")
time.Sleep(1 * time.Second)
}
}()
//主go程
for i := 0; i < 10; i++ {
fmt.Println("-----I'm main-----")
time.Sleep(1 * time.Second)
if i == 2 {
break
}
}
}
1.3.2runtime包
runtime.Gosched():出让当前go程所占用的cpu时间片,再次获得cpu时,从出让位置继续执行
package main
import (
"fmt"
"runtime"
)
func main() {
go func() {
for {
fmt.Println("this is goroutine test ")
}
}()
for {
runtime.Gosched() //出让当前cpu时间片
fmt.Println("this is main test")
}
}
runtime.Goexit():结束调用该函数的当前go程。Goexit0):之前注册的 defer都生效。
return:返回当前函数调用到调用者那里去。retumn之前的 defer 注册生效。
package main
import (
"fmt"
"runtime"
)
func test() {
defer fmt.Println("cccccccc")
//return
runtime.Goexit() //退出当前go程
fmt.Println("ddddddd")
}
func main() {
go func() {
defer fmt.Println("aaaaaaaa")
test()
fmt.Println("bbbbbbb")
}()
for {
;
}
}
runtime.GOMAXPROCS():设置当前进程使用的最大cpu核数,返回上一次调用成功的设置值,首次返回默认值
package main
import (
"fmt"
"runtime"
)
func main() {
n := runtime.GOMAXPROCS(1) //单核
fmt.Println("n=", n)
n = runtime.GOMAXPROCS(2) //双核
fmt.Println("n=", n)
for i := 0; i < 10; i++ {
go fmt.Println(0) //子go程
fmt.Println(1) //主go程
}
}
2.协程间通信与Channel
2.1channel
概念:是一种数据类型,对应一个“管道”主要用来解决go程的同步问题以及协程之间数据共享(数据传递)的问题。通过通信来共享内存,而不是共享内存来通信
定义:make(chan 在channel中传递的数据类型,容量) 容量=0:无缓冲channel || 容量>0:有缓存channel
【补充知识】:每当有一个进程启动时,系统会自动打开三个文件:标准输入(stdin)、标准输出(stdout)、标准错误(stderr),当进程运行结束,操作系统自动关闭三个文件

channel有两个端:
一端:写端(传入端) chan <-
另一端:读端(传出端) <- chan
要求:读端和写端必须同时满足条件,才在channel上进行数据流动,否则,则阻塞
package main
import (
"fmt"
"time"
)
// 全局定义channel,用来完成数据同步
var channel = make(chan int)
// 定义一台打印机
func Printer(s string) {
for _, ch := range s {
fmt.Printf("%c", ch) //屏幕:stdout
time.Sleep(300 * time.Millisecond)
}
}
// 定义两个人使用打印机
func person1() { //先执行
Printer("hello")
channel <- 1
}
func person2() { //后执行
<-channel
Printer("world")
}
func main() {
go person1()
go person2()
for {
;
}
}
channel同步,数据传递:
package main
import "fmt"
func main() {
ch := make(chan string) //无缓冲channel
//len(ch):channel中剩余未读取数据个数,cap(ch):channel容量
fmt.Println("len=", len(ch), "cap=", cap(ch))
go func() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
//通知主go打印完成
ch <- "done"
}()
fmt.Println(<-ch)
}
无缓冲channel(同步通信):通道容量为0,len=0,应用于两个go程中,一个读另一个写,具备同步的能力(打电话)

有缓冲channel(异步通信):通道容量为0,应用于两个go程中,一个读另一个写,缓冲区可以进行数据存储,存储至容量上限,阻塞。具备异步能力,不需同时操作缓冲区(发短信)
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 5) //存满3个元素之前,不会阻塞
fmt.Println("len=", len(ch), "cap=", cap(ch))
go func() {
for i := 0; i < 8; i++ {
ch <- i
fmt.Println("子go程", i, "len=", len(ch), "cap=", cap(ch))
}
}()
time.Sleep(3 * time.Second)
for i := 0; i < 8; i++ {
num := <-ch
fmt.Println("主go程读到", num)
}
}
关闭channel:
确定不再对端发送、传输数据,使用close(ch)关闭channel
对端可以判断channel是否关闭:
if num,ok := <-ch; ok == true{
如果对端已经关闭, ok --> false . num无数据
如果对端没有关闭,ok--> true . num保存读到的数据
可以使用range替代ok
总结:1.数据不发送完不应该关闭
2.已经关闭的channel,不能再向其写入数据,可以从中读取数据,无缓冲(读到0)
有缓冲(缓冲区内有数据,先读数据,读完数据后,可以继续读,读到0)
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch) //写端,写完数据主动关闭channel
fmt.Println("读端关闭")
}()
time.Sleep(2 * time.Second)
//for {
// if num, ok := <-ch; ok == true {
// fmt.Println("读到数据:", num)
// } else {
// n := <-ch
// fmt.Println("关闭后:", n)
// break
// }
//}
for num := range ch {
fmt.Println("读到数据:", num)
}
}
单向channel:
默认的channel是双向的。 var ch chan int ch = make(chan int)
单向写channel:var sendCh chan <- int sendCh = make(chan <- int) 不能读操作
单向读channel:var recvCh <- chan int recvCh = make(<- chan int)
转换:
双向channel可以隐式转换为任意一种单项channel
sendCh = ch
单向channel不能转换为双向channel
ch = sendCh/recvCh error!!
传参:传【引用】
package main
import "fmt"
func send(out chan<- int) {
out <- 1
close(out)
}
func recv(in <-chan int) {
n := <-in
fmt.Println(n)
}
func main() {
ch := make(chan int) //双向channel
go func() {
send(ch) //双向channel转为写入channel
}()
recv(ch)
}
2.2单向channel及应用


生产者:发送数据端
消费者:接收数据端
缓冲区:1.解耦(降低生产者和消费者之间耦合度)
2.并发(生产者消费者数量不对等时,能保持正常通信)
3.缓存(生产者和消费者数据处理速度不一致时,暂存数据)
package main
import (
"fmt"
"time"
)
func Producer(out chan<- int) {
for i := 0; i < 10; i++ {
fmt.Println("生产者生产:", i)
out <- i * i
}
close(out)
}
func Consumer(in <-chan int) {
for num := range in {
fmt.Println("消费者拿到:", num)
time.Sleep(1 * time.Second)
}
}
func main() {
ch := make(chan int)
go Producer(ch) //子go程 生产者
Consumer(ch) //主go程 消费者
}
订单模拟:
package main
import "fmt"
type OrderInfo struct {
id int
}
func Producer(out chan<- OrderInfo) {
for i := 0; i < 10; i++ {
order := OrderInfo{id: i}
out <- order
}
close(out)
}
func Consumer(in <-chan OrderInfo) {
for order := range in {
fmt.Println("订单id为:", order.id)
}
}
func main() {
ch := make(chan OrderInfo)
go Producer(ch)
Consumer(ch)
}
2.3定时器
2.3.1time.Timer
time.Timer:创建定时器,指定定时时长,定时到达后。系统会自动向定时器的成员C写系统当前时间。(对 chan 的写操作)
type Time struct{
C <- chan Time
r runtime Timer
}
读取 Timer.C 得到 定时后的系统时间。并且完成一次 chan 的 读操作
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("当前时间:", time.Now())
//创建定时器
myTimer := time.NewTimer(time.Second * 2)
nowTime := <-myTimer.C //chan类型
fmt.Println("现在时间:", nowTime)
}
三种定时方法:
package main
import (
"fmt"
"time"
)
// 三种定时方法
func main() {
//1.sleep
time.Sleep(time.Second)
//2.Timer.C
myTimer := time.NewTimer(time.Second * 2) //创建一个定时器,指定定时时长
nowTime := <-myTimer.C //定时满,系统自动写入系统时间
fmt.Println("现在时间:", nowTime)
//3.time.After
nowTime2 := <- time.After(time.Second)
fmt.Println("现在时间:", nowTime2)
}
定时器的停止和重置:
package main
import (
"fmt"
"time"
)
func main() {
myTimer := time.NewTimer(time.Second * 3) //创建定时器
myTimer.Reset(time.Second * 1) //重置定时时长为1
go func() {
<-myTimer.C
fmt.Println("子go程定时完毕")
}()
myTimer.Stop() //设置定时器停止 <-myTimer.C会阻塞
for {
;
}
}
2.3.2time.Ticker
time.Ticker:定时时长到达后,系统会自动向 Ticker的 C中写入系统当前时间。并且,每隔一个定时时长后,循环写入系统当前时间。在子go程中循环读取 C,获取系统写入的时间,
type Ticker struct{
C <- chan Time
r runtime Timer
}
周期定时:
package main
import (
"fmt"
"time"
)
func main() {
quit:= make(chan bool) //创建一个判断是否终止的channel
fmt.Println("now:", time.Now())
myTicker := time.NewTicker(time.Second * 1)
i:=0
go func() {
for {
nowTime := <-myTicker.C
i++
fmt.Println("nowTime:", nowTime)
if i==3{
quit <- true //解除主go程阻塞
}
break//return//runtime.Goexit()
}
}()
<-quit //子go程循环获取<-myTicker.C期间,一直阻塞
}
3.并发编程与同步机制
3.1select


select监听channel通信:
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
ch := make(chan int) //用来进行数据通信的channel
quit := make(chan bool) //用来判断是否退出的channel
go func() { //写数据
for i := 0; i < 5; i++ {
ch <- i
time.Sleep(1 * time.Second)
}
close(ch)
quit <- true //通知主go程退出
runtime.Goexit()
}()
for { //主go程,读数据
select {
case num := <-ch:
fmt.Println("读到:", num)
case <-quit:
return //终止进程
}
fmt.Println("=========")
}
}

select实现斐波那契数列:
package main
import (
"fmt"
"runtime"
)
func fibonacci(ch <-chan int, quit <-chan bool) {
for {
select {
case n := <-ch:
fmt.Print(n, " ")
case <-quit:
runtime.Goexit()
}
}
}
func main() {
ch := make(chan int)
quit := make(chan bool)
go fibonacci(ch, quit) //子go程 打印fibonacci数列
x, y := 1, 1
for i := 0; i < 20; i++ {
ch <- x
x, y = y, x+y
}
quit <- true
}
超时处理:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
quit := make(chan bool)
go func() { //子go程获取数据
for {
select {
case v := <-ch:
fmt.Println("Received:", v)
case <-time.After(3 * time.Second):
quit <- true
goto lable
}
}
lable:
fmt.Println("Exit")
}()
for i := 0; i < 2; i++ {
ch <- i
time.Sleep(2 * time.Second)
}
<-quit //主go程,阻塞等待 子go程通知,退出
fmt.Println("Done")
}
3.2锁和条件变量
3.2.1锁
死锁:不是一种锁,是一种错误使用锁导致的现象
1.单go程自己死锁:channel应该在最少两个以上go程中进行通信
2.go程间channel访问顺序导致死锁:使用channel一端读(写),要保证另一端写(读)操作同时有机会执行
3.多go程,多channel交叉死锁:Ago程:掌握M,尝试拿N Bgo程:掌握N,尝试拿M
4.在go语言中,尽量不要将互斥锁、读写锁与channel混用--隐性死锁
package main
func main() {
//死锁1
//ch := make(chan int)
//ch <- 789
//num := <-ch
//fmt.Println(num)
//死锁2
//ch := make(chan int)
//num := <-ch
//fmt.Println(num)
//go func() {
// ch <- 789
//}()
//死锁3
// ch1 := make(chan int)
// ch2 := make(chan int)
// go func() {
// for {
// select {
// case num := <-ch1:
// ch2 <- num
// }
// }
// }()
// for {
// select {
// case num := <-ch2:
// ch1

最低0.47元/天 解锁文章
1366






