《Golang——21.docx》由会员分享,可在线阅读,更多相关《Golang——21.docx(20页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Golang2121 并发编程 目录 21 并发编程 21.1 概述 21.2 goroutine 21.3 channel 21.3.1 channel类型 21.3.2 无缓冲channel 21.3.3 有缓冲channel 21.3.4 range以及close 21.3.5 单方向channel 21.3.6 定时器 Timer Ticker 21.4 select 21.4.1 select作用 21.4.2 超时 21.1 概述 21.1.1 并行以及并发 并行 parallel 指在同一时刻 有多条指令在多个处理器上同时执行。 并发 concurrency 指在同一时刻只能有一
2、条指令执行 但多个进程指令被快速的轮换执行 使得在宏观上具有多个进程同时执行的效果 但在微观上并不是同时执行的 只是把时间分成假设干段 使多个进程快速交替的执行。 并行是两个队列同时使用两台咖啡机并发是两个队列交替使用一台咖啡机 21.1.2 Go语言并发优势 有人把Go比作21世纪的C语言 第一是因为Go语言设计简单 第二 21世纪最重要的就是并发程序设计 而Go从语言层面就支持了并发。同时 并发程序的内存管理有时候是非常复杂的 而Go语言提供了自动垃圾回收机制。 Go语言为并发编程而内置的上层API基于CSP communication sequential process 顺序通信进程
3、模型。这就意味着显式锁都是可以防止的 因为Go语言通过平安的通道发送以及承受数据以实现同步 这大大地简化了并发程序的编写。 一般情况下 一个普通的桌面计算机跑十几二十个线程就有点负载过大了 但是同样这台机器却可以轻松地让成百上千甚至过万个goroutine进展资源竞争。 21.2 goroutine 21.2.1 goroutine是什么 goroutine是Go并发设计的核心。goroutine讲到底其实就是就是协程 但是它比线程更小 十几个goroutine可能表达在底层就是五六个线程 Go语言内部帮你实现了这些goroutine之间的内存分享。执行goroutine只需极少的内存 大概是
4、45KB 当然会根据相应的数据伸缩。也正因为如此 可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。 21.2.2 创立goroutine 只需要在函数调用语句前添加go关键字 就可以创立并发执行单元。开发人员无需解析任何执行细节 调度器会自动将其安排到适宜的系统线程上执行。 在并发编程里 我们通常想将一个经过切分成几块 然后让每个goroutine各自负责一块工作。当一个程序启动时 其主函数即在一个单独的goroutine中运行 我们叫它main goroutine。新的goroutine会用go语句来创立。 例如 package mainimport (
5、 fmt time )func main() go newTask()/新建一个goroutine for fmt.Println( this is a main goroutine. ) time.Sleep(time.Second) func newTask() for fmt.Println( this is a new Task. ) time.Sleep(time.Second)/延时1s 以上实例运行结果为 this is a main goroutine.this is a new Task.this is a new Task.this is a main goroutine.
6、this is a main goroutine.this is a new Task.21.2.3 主goroutine先退出 主协程退出了 其他子协程也要跟着退出。 实例 package mainimport ( fmt time )func main() go func () i: 0 for fmt.Println( this is a new Task : ,i) time.Sleep(time.Second) i () i : 0 for fmt.Println( this is a main goroutine : ,i) time.Sleep(time.Second) i if
7、 i 2 break 以上实例运行结果为 this is a main goroutine : 0this is a new Task : 0this is a new Task : 1this is a main goroutine : 1 主协程先退出导致子协程没有来得及调用 package mainimport ( fmt time )func main() go func () i: 0 for fmt.Println( this is a new Task : ,i) time.Sleep(time.Second) i () 11.2.4 runtime包 Gosched runti
8、me.Gosched()用于让出CPU时间片 让出当前goroutine的执行权限 调度器安排其他等待的任务运行 并在下次某个时候从该位置恢复执行。 这就像跑接力赛 A跑了一会碰到代码runtime.Gosched()就把接力棒交给B了 A歇着了 B继续跑。 实例 package mainimport ( fmt runtime )func main() go func () for i: i i fmt.Println( Oh! ) () for i: i i /让出时间片 先让别的协程执行 执行完了 再回来执行此协程 runtime.Gosched() fmt.Println( Yeah!
9、 ) Goexit 调用runtime.Goexit()将立即终止当前goroutine执行 调度器确保所有已注册defer延迟调用被执行。 package mainimport ( fmt runtime )func main() /创立协程 go func() fmt.Println( En. ) /调用函数 test() fmt.Println( Oops. ) () /不让主协程完毕 forfunc test() defer fmt.Println( Yeah! ) runtime.Goexit()/终止所在的协程 fmt.Println( Oh! ) GOMAXPROCS 调用run
10、time.GOMAXPROCS()用来设置可以并行计算的CPU核数的最大值 并返回之前的值。 package mainimport ( fmt runtime )func main() n: runtime.GOMAXPROCS(1) /把参数改为2试一试 fmt.Println( n ,n) for go fmt.Print(0) fmt.Print(1) 在第一次执行 runtime.GOMAXPROCS(1) 时 最多同时只能有一个goroutine被执行。所以会打印很多1。过了一段时间后 Go调度器会将其置为休眠 并唤醒另一个goroutine 这时候就开场打印很多0了 在打印的时候
11、goroutine是被调度到操作系统线程上的。 在第二次执行 runtime.GOMAXPROCS(2) 时 我们使用了两个CPU 所以两个goroutine可以一起被执行 以同样的频率交替打印0以及1。 多任务资源竞争问题 package mainimport ( fmt time )func Printer(str string) for _,data: range str fmt.Printf( %c ,data) time.Sleep(time.Second) fmt.Printf( n )func person1() Printer( Oh! )func person2() Prin
12、ter( Yeah! )func main() /新建2个协程 代表2个人。两个人共同使用打印机 go person1() go person2() /不让主协程完毕 for 21.3 channel goroutine运行在一样的地址空间 因此访问分享内存必须做好同步。goroutine奉行通过通信来分享内存 而不是分享内存来通信。 引用类型channel是CSP形式的详细实现 用于多个goroutine通讯。其内部实现了同步 确保并发平安。 21.3.1 channel类型 定义一个channel时 也需要定义发送到channel的值的类型。channel可以使用内置的make()函数来创
13、立 make(chan Type) /等价于make(chan Type,0)make(chan Type,capacity) 当capacity 0时 channel是无缓冲阻塞读写的 当capacity 0时 channel有缓冲、是非阻塞的 直到写满capacity个元素才阻塞写入。 channel通过操作符 -来接收以及发送数据 发送以及接收数据语法 channel - value /发送value到channel - channel /接收并将其丢弃x : -channel /从channel中接收数据 并赋值给xx,ok : -channel /功能同上 同时检查通道是否已关闭或是
14、否为空 默认情况下 channel接收以及发送数据都是阻塞的 除非另一端已经准备好 这样就使得goroutine同步变得更加简单 而不需要显示的lock。 实例 package mainimport ( fmt time )var ch make(chan int)func Printer(str string) for _,data: range str fmt.Printf( %c ,data) time.Sleep(time.Second) fmt.Printf( n )/person1执行完成 才到person2执行func person1() Printer( Oh! ) ch -0
15、 /给管道/通道写数据 发送func person2() -ch /从管道取数据 接收 假如通道没有数据它就会阻塞 Printer( Yeah! )func main() /新建2个协程 代表2个人。两个人共同使用打印机 go person1() go person2() /不让主协程完毕 for以上实例执行结果为 Oh!Yeah! 通过channel实现同步以及数据交互。 实例 package mainimport ( fmt time )func main() defer fmt.Println( 主协程完毕。 ) ch : make(chan string) go func() defe
16、r fmt.Println( 子协程调用完毕。 ) for i : i i fmt.Println( 子协程 i , i) time.Sleep(time.Second) ch - 子协程干活儿了。 /把这行注释掉再运行一下 看看什么结果 () str : -ch /没有数据前 阻塞 fmt.Println( str , str)以上实例执行结果为 子协程 i 0子协程 i 1子协程调用完毕。str 子协程干活儿了。主协程完毕。 21.3.2 无缓冲的channel 无缓冲的通道 unbuffersd channel 是指在接收前没有才能保存任何值的通道。 这种类型的通道要求发送gorouti
17、ne以及接收goroutine同时准备好 才能完成发送以及接收操作。假如两个goroutine没有同时准备好 通道会导致先执行发送或者接收操作的goroutine阻塞等待。 这种对通道进展发送以及接收的交互行为本身就是同步的。其中任意一个操作都无法分开另一个操作单独存在。 下列图展示两个goroutine怎样利用无缓冲的通道来分享一个值 在第1步 两个goroutine都到达通道 但哪个都没有开场执行发送或接收。在第2步 左侧的goroutine将它的手伸进了通道 这模拟了向通道发送数据的行为。这时 这个goroutine会在通道中被锁住 直到交换完成。在第3步 右侧的goroutine将它的
18、手放入通道 这模拟了从通道里接收数据。这个goroutine一样也会在通道中被锁住 直到交换完成。在第4步以及第5步 进展交换 并最终在第6步 两个goroutine都将它们的手从通道里拿出来 这模拟了被锁住的goroutine得到释放。两个goroutine如今都可以去做别的事情了。无缓冲的channel创立格式 make(chan Type) /等价于make(chan Type,0) 假如没有指定缓冲区容量 那么该通道就是同步的 因此会阻塞到发送者准备好发送以及接收者准备好接收。实例 package mainimport ( fmt time )func main() /创立一个无缓存的
19、channel ch : make(chan int,0) /len(ch)缓冲区剩余数据个数 cap(ch)缓冲区大小 fmt.Printf( len(ch) %d,cap(ch) %dn ,len(ch),cap(ch) /新建协程 go func() for i: i i fmt.Println( 子协程 i ,i) ch - i () /延时 time.Sleep(2*time.Second) for i: i i num : -ch /读取管道中内容 没有内容前 阻塞 fmt.Println( num ,num) 以上实例执行结果为 len(ch) 0,cap(ch) 0子协程 i
20、0num 0子协程 i 1子协程 i 2num 1num 2 21.3.3 有缓冲的channel 有缓冲的通道 buffered channel 是一种在被接收前能存储一个或者多个值的通道。 这种类型的通道并不强迫要求goroutine之间必须同时完成发送以及接收。通道会阻塞发送以及接收动作的条件也会不同。只有在通道中没有要接收的值时 接收动作才会阻塞。只有在通道没有可用缓冲区包容被发送的值时 发送动作才会阻塞。 这导致有缓冲的通道以及无缓冲的通道之间的一个很大的不同 无缓冲的通道保证进展发送以及接收的goroutine会在同一时间进展数据交换 有缓冲的通道没有这种保证。 在第1步 右侧的g
21、oroutine正在从通道接收一个值。在第2步 右侧的这个goroutine独立完成了接收值的动作 而左侧的goroutine正在发送一个新值到通道里。在第3步 左侧的goroutine还在向通道发送新值 而右侧的goroutine正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的 也不是相互阻塞。在第4步 所有的发送以及接收都完成 而通道里还有几个值 也有一些空间可以存更多的值。 有缓冲的channel创立格式 make(chan Type,capicity) 假如给定了一个缓冲区容量 通道就是异步的。只要缓冲区有未使用空间用于发送数据 或者还包含可以接收的数据 那么其通信就会无阻塞
22、地进展。 实例 package mainimport fmt func main() /创立一个有缓存的channel 容量为3 ch : make(chan int, 3) fmt.Printf( len(ch) %d,cap(ch) %d , len(ch), cap(ch)输出结果为 len(ch) 0,cap(ch) 3实例 package mainimport ( fmt time )func main() /创立一个有缓存的channel 容量为3 ch : make(chan int, 3) fmt.Printf( len(ch) %d,cap(ch) %dn , len(ch)
23、, cap(ch) /新建协程 go func() for i : i i /改成i 10试试 ch - i /不会阻塞 ch容量为3 fmt.Printf( 子协程%d len(ch) %d,cap(ch) %dn , i, len(ch), cap(ch) () /延时 time.Sleep(2 * time.Second) for i : i i /改成i 10试试 num : -ch /读取管道中内容 没有内容前 阻塞 fmt.Println( num , num) 输出结果为 len(ch) 0,cap(ch) 3子协程0 len(ch) 1,cap(ch) 3子协程1 len(ch
24、) 2,cap(ch) 3子协程2 len(ch) 3,cap(ch) 3num 0num 1num 2 21.3.4range以及closeclose的用法 package mainimport ( fmt )func main() /创立一个无缓存的channel ch : make(chan int) fmt.Printf( len(ch) %d,cap(ch) %dn , len(ch), cap(ch) /新建协程 go func() for i : i i ch - i /往通道写数据 /不需要再写数据 关闭channel close(ch) ch - 5 /关闭channel后无
25、法再发送数据 () for /假如ok为true 讲明通道没有关闭 if num,ok: ok true fmt.Println( num ,num) else /通道关闭 /fmt.Println(num) break 上述实例打印结果为 len(ch) 0,cap(ch) 0num 0num 1num 2num 3num 4注意点 channel不像文件一样需要经常去关闭 只有当你确定没有任何发送数据了 或你想显式地完毕range循环之类的 才去关闭channel 关闭channel后 无法向channel再发送数据 引发panic错误后导致接收立即返回零值 关闭channel后 可以继续
26、从channel接收数据 对于nil channel 无论收发都会被阻塞。 range的用法 package mainimport ( fmt )func main() /创立一个无缓存的channel ch : make(chan int) fmt.Printf( len(ch) %d,cap(ch) %dn , len(ch), cap(ch) /新建协程 go func() for i : i i ch - i /往通道写数据 /不需要再写数据 关闭channel close(ch) /ch - 5 /关闭channel后无法再发送数据 () for num: range ch /可以自
27、动跳出循环 fmt.Println( num ,num) 上述实例打印结果为 len(ch) 0,cap(ch) 0num 0num 1num 2num 3num 4 21.3.5单方向的channel 默认情况下 通道是双向的 也就是 既可以往里面发送数据可以以从里面取出数据。 但是 我们经常见一个通道作为参数进展值传递而且祈望对方是单向使用的 要么只让它发送数据 要么只让它接收数据 这时候我们可以指定通道的方向。 单向channel变量的声明非常简单 如下 var ch1 chan int /ch1是一个正常的channel 不是单向的var ch2 chan - float64 /ch2
28、是单向channel 只用于写float64数据var ch3 -chan int /ch3是单向channel 只用于读取int数据chan - 表示数据进入管道 要把数据写进管道 对于调用者就是输出。 -chan 表示数据从管道出来 对于调用者就是得到管道的数据 当然就是输入。package mainfunc main() /创立一个channel 双向的 ch : make(chan int) /双向channel能隐式转换为单向channel var writeCh chan - int ch /只能写 /var readCh -chan int ch /只能读 /writeCh -
29、6 / -writeCh /invalid operation: -writeCh (receive from send-only type chan - int) /close(writeCh) / -readCh /readCh - 6 /invalid operation: readCh - 6 (send to receive-only type -chan int) /单向无法转换为双向 var ch1 chan int writeCh /cannot use writeCh (type chan - int) as type chan int in assignment 可以将ch
30、annel隐式转换为单向队列 只收或者只发 不能将单向channel转换为普通channel。 实例 package mainimport fmt func main() /创立一个双向通道 ch : make(chan int) /消费者 消费数字 写入channel /新开一个协程 go producer(ch) /channel传参 引用传递 /消费者 从channel读内容 consumer(ch)/此channel只能写func producer(in chan - int) for i: i i in -i close(in)/此channel只能读func consumer(ou
31、t -chan int) for num : range out fmt.Println( num ,num) 上述实例打印结果为 num 0num 1num 2num 3num 4num 5num 6num 7num 8num 9 21.3.6定时器1.Timer Timer是一个定时器 代表将来的一个单一事件 你可以告诉timer你要等待多长时间 它提供一个channel 在将来的那个时间那个channel提供了一个时间值。 time.MewTimer()方法 package mainimport ( fmt time )func main() /创立一个定时器 设置时间为2s 2s后往t
32、ime通道写内容 当前时间 timer : time.NewTimer(2*time.Second) fmt.Println( Current time : ,time.Now() / 2s后 往timer.C写数据 有数据后 就可以读取 t : -timer.C /channel没有数据前后阻塞 fmt.Println( t ,t) 上述实例打印结果为 Current time : 2018-05-25 19:06:32.3679043 0800 CST m 0.005014201t 2018-05-25 19:06:34.3681931 0800 CST m 2.005303101 tim
33、e.NewTimer()时间到了 只会响应一次 package mainimport ( fmt time )func main() /创立一个定时器 设置时间为2s 2s后往time通道写内容 当前时间 timer : time.NewTimer(2*time.Second) for -timer.C /只会写一次 然后就阻塞 死锁报错 fmt.Println( Time out. ) 上述实例输出结果为 Time out.fatal error: all goroutines are asleep - deadlock! time.Sleep()方法 package mainimport
34、( fmt time )func main() /延时2s后打印 time.Sleep(2*time.Second) fmt.Println( Time out. )2s后打印 Time out. time.After()方法 package mainimport ( fmt time )func main() -time.After(2*time.Second) /定时2s 阻塞2s 2s后产生一个事件 往channel写内容 fmt.Println( Time out. ) 2s后打印 Time out. time的停用 package mainimport ( fmt time )fun
35、c main() timer : time.NewTimer(3*time.Second) go func() -timer.C fmt.Println( Time out. ) () timer.Stop() /停顿定时器 for time的重置 package mainimport ( fmt time )func main() timer : time.NewTimer(3*time.Second) timer.Reset(1*time.Second) -timer.C fmt.Println( Time out. )2.Ticker Ticker是一个定时触发的计时器 它会以一个间隔(
36、interval)往channel发送一个事件 当前时间 而channel的接收者可以以固定的时间间隔从channel中读取事件。 实例 package mainimport ( fmt time )func main() ticker : time.NewTicker(1*time.Second) i: 0 for -ticker.C i fmt.Println(i) 上述实例输出结果为 123456789. ticker的停顿 package mainimport ( fmt time )func main() ticker : time.NewTicker(1*time.Second)
37、i: 0 for -ticker.C fmt.Println(i) i if i 5 ticker.Stop() break 上述实例输出结果为 01234 21.4 select Go里面提供了一个关键字select 通过select可以监听channel上的数据流动。 select的用法与switch语言非常类似 由select开场一个新的选择块 每个选择条件由case语句来描绘。 与switch语句可以选择任何可使用相等比拟的条件相比 select有比拟多的限制 其中最大的一条限制就是每个case语句里必须是一个IO操作 大致的构造如下 selectcase -chan1: /假如cha
38、n1成功读到数据 那么进展case处理语句case chan2 -1: /假如成功向chan2写入数据 那么进展该case处理语句default: /假如上面都没有成功 那么进入default处理流程 在一个select语句中 Go语言会按顺序从头至尾评估每一个发送以及接收的语句。 假如其中的任意一语句可以继续执行 即没有被阻塞 那么就从那些可以执行的语句中任意选择一条来使用。 假如没有任意一条语句可以执行 即所有的通道都被阻塞 那么有两种可能的情况 假如给出了default语句 那么就会执行default语句 同时程序的执行会从select语句后的语句中恢复。假如没有default语句 那么
39、select语句将被阻塞 直到至少有一个通信可以进展下去。通过select实现斐波那契数列数列 package mainimport ( fmt )func fibonacci(ch chan - int,quit -chan bool) x,y : 1,1 for /监听channel数据的流动 select case ch - x: x,y y,x y case flag: -quit: fmt.Println( flag ,flag) return func main() ch: make(chan int) /数字通信 quit: make(chan bool) /消费者 从chann
40、el读取内容 /新建协程 go func() for i: i i num: -ch fmt.Println(num) /可以停顿 quit -true () /消费者 消费数字 写入channel fibonacci(ch,quit) 上述实例输出结果为 11235813213455flag true 有时候会出现goroutine阻塞的情况 那么我们怎样防止整个程序进入阻塞的情况呢 我们可以利用select来设置超时 通过如下的方式实现 package mainimport ( fmt time )func main() ch : make(chan int) quit : make(ch
41、an bool) / 新开一个协程 go func() for ; ; select case v : -ch: fmt.Println(v) case -time.After(3*time.Second): fmt.Println( Timeout. ) quit -true break () /往ch中存放数据 for i: i i ch -i time.Sleep(time.Second) -quit fmt.Println( It is the end of the program. ) 上述实例输出结果为 01234Timeout.It is the end of the program. 新浪微博 古老医麦 技术沟通论坛 :/ yinchengxueyuan /forum.php