Go并发编程 Golang: Let gopher run faster 15/1/13 分享者:郝林 1 v1.0.0 关于 我 15/1/13 宜信 - 微佳 2 郝林,年龄33-,码龄9年+,Go语言非脑残粉 (@特价萝卜) ²  Java 软件工程师 ²  Clojure/Python 程序员 ²  Linux 爱好者 ²  NBA 观众 关于 《Go并发编程实战》 15/1/13 宜信 - 微佳 3 2013年7月受图灵公司之邀开始撰写书稿,2014年7月提交初 稿,2014年11月出版。历经16个月。 ²  分享Go语言编程知识 ²  探究Go语言并发编程原理 ²  为Go语言中文社区做一点贡献 ²  找一个深入理解Go语言的理由 http://www.ituring.com.cn/book/1525 目录 15/1/13 宜信 - 微佳 4 •  Go语言与并发编程 1 •  Goroutine与go语句 2 •  Channel 3 •  并发编程原理一窥 4 •  一些控制参数 5 •  推荐阅读列表 6 Go语言是怎样的? 15/1/13 宜信 - 微佳 5 ü  开源软件,社区活跃、迭代快速 ü  通用、系统级、跨平台 ü  静态类型、编译型,脚本化的语法 ü  支持多种编程范式(函数式 + 面向对象) ü  提供了强大、易用的工具,涵盖软件的全生命周期 ü  原生的并发编程支持 ü  在大大提高开发效率的同时拥有极高的运行效率 Go语言的哲学 15/1/13 宜信 - 微佳 6 ü  崇尚简约 ü  约定大于配置(但配置也不可或缺) ü  在编码规范上严谨,在软件设计上宽松 ü  以通讯的方式共享内存,而不是将共享内存作为通讯手段 ü  软件工程至上! Go语言与并发编程(1) 15/1/13 宜信 - 微佳 7 Goroutine是Go语言用于对并发编程提供原生 支持的利器,也是其并发编程模型的核心概念 之一。 Go语言的开发者们之所以专门创建了这样的一个名词,是因为 他们认为现存的进程(Process)、线程(Thread)、协程 (Coroutine)等概念都向应用程序员们传达了错误的信号。为 了与它们有所区别,Goroutine这个词得以诞生。 Go语言与并发编程(2) 15/1/13 宜信 - 微佳 8 多进程编程: Ø  管道(Pipe) Ø  信号(Signal) Ø  消息队列(︎︎︎︎︎Message Queue) Ø  ︎信号灯(Semaphore) Ø  共享内存︎︎︎区︎(Shared Memory) Ø  套接字(Socket) Go语言与并发编程(3) 15/1/13 宜信 - 微佳 9 多线程编程: Ø  共享内存︎︎︎区︎(Shared Memory) ü  互斥量(Mutex) ü  条件变量(Conditions) ü  原子操作(Atomic operation) ü  可重入函数(Reentrant function) Go语言与并发编程(4) 15/1/13 宜信 - 微佳 10 Go语言特有的并发编程方式: Goroutine & Channel Goroutine与go语句(1) 15/1/13 宜信 - 微佳 11 Goroutine是怎样的? 笼统地讲,Goroutine基于两级线程模型,可以被视为“用户 级线程”。但它背后的支撑体系比之更加复杂和高效。 轻量级线程 ?绿色线程 ? 协程?超级协程 ? ✗ Goroutine与go语句(2) 15/1/13 宜信 - 微佳 12 与Goroutine有关的几个数字: ² 一个Goroutine栈的初始尺寸:2 KB (相比之下,一个线程栈默认需要 8 MB) ² 一个Goroutine栈的最大尺寸: Ø  在 32 位系统下为:250 MB Ø  在 64 位系统下为:1 GB ² 默认情况下,N(几十上百万)个Goroutine最多可以在 10000 个内核线程上调度运行。 Goroutine与go语句(3) 15/1/13 宜信 - 微佳 13 怎样创建或启用一个Goroutine? go println("Go! Goroutine!") go func() { println("Go! Goroutine!") }() Goroutine与go语句(4) 15/1/13 宜信 - 微佳 14 有一点需要特别注意: names := []string{"Eric", "Harry", "Robert", "Jim", "Mark"} for _, name := range names { go func() { fmt.Printf("Hello, %s.\n", name) }() } 输出: Hello, Mark. Hello, Mark. Hello, Mark. Hello, Mark. Hello, Mark. ✗ Goroutine与go语句(5) 15/1/13 宜信 - 微佳 15 正确的做法是: names := []string{"Eric", "Harry", "Robert", "Jim", "Mark"} for _, name := range names { go func(who string) { fmt.Printf("Hello, %s.\n", who) }(name) } 输出: Hello, Eric. Hello, Harry. Hello, Robert. Hello, Jim. Hello, Mark. ✓ Goroutine与go语句(6) 15/1/13 宜信 - 微佳 16 让我们散焦一点: package main import ( "fmt" "runtime" ) func main() { names := []string{"Eric", "Harry", "Robert", "Jim", "Mark"} for _, name := range names { go func(who string) { fmt.Printf("Hello, %s.\n", who) }(name) } } ✗ Goroutine与go语句(7) 15/1/13 宜信 - 微佳 17 手动调度Goroutine: package main import ( "fmt" "runtime" ) func main() { names := []string{"Eric", "Harry", "Robert", "Jim", "Mark"} for _, name := range names { go func(who string) { fmt.Printf("Hello, %s.\n", who) }(name) } runtime.Gosched() } ✓ Goroutine与go语句(8) 15/1/13 宜信 - 微佳 18 主Goroutine做了什么? ü  启动系统监测器 ü  设定通用配置,检查运行环境 ü  创建定时垃圾回收器 ü  执行main包的init函数 ü  执行main包的main函数 ü  进行一些善后处理工作 Channel(1) 15/1/13 宜信 - 微佳 19 Channel是怎样的? ü  即通道类型,Go语言的预定义类型之一 ü  类型化、并发安全的通用型管道 ü  用于在多个Goroutine之间传递数据 ü  对于“以通讯的方式共享内存”的最直接体现 Channel 与 Goroutine 堪称绝配! Channel(2) 15/1/13 宜信 - 微佳 20 怎样创建Channel? strChan := make(chan string, 3) strChan := make(chan string) // 相当于 strChan := make(chan string, 0) Channel(3) 15/1/13 宜信 - 微佳 21 怎样向通道发送一个值? 怎样从通道接收一个值? strChan <- "first value" value := <- strChan // 或 value, ok := <- strChan Channel(4) 15/1/13 宜信 - 微佳 22 怎样关闭通道? 注意! (1)  试图从一个未被初始化的通道接收值,会造成当前Goroutine的永 久阻塞! (2)  试图向一个未被初始化的通道发送值,也会造成当前Goroutine的 永久阻塞! close(strChan) Channel(5) 15/1/13 宜信 - 微佳 23 注意!(续) (3)  试图向一个已被关闭的通道发送值,会立即引发一个运行时恐慌! (4)  试图关闭一个已被关闭的通道,也会立即引发一个运行时恐慌! (5)  被通道传递的是值的副本,而不是值本身。 (6)  关闭通道不会使针对于此的接收操作立即结束。 还记得 value, ok := <- strChan 吗? Channel(6) 15/1/13 宜信 - 微佳 24 Happens before 原则 【针对缓冲(长度大于零的)通道:make(chan string, 3) 】 发送操作开始 拷贝值(产生副本) 发送操作结束 接收操作开始 接收方持有值 接收操作结束 Channel(7) 15/1/13 宜信 - 微佳 25 Happens before 原则 【针对非缓冲(长度等于零的)通道:make(chan string) 】 发送操作开始 接收操作开始 拷贝值(产生副本) 接收方持有值 接收操作结束 发送操作结束 Channel(8) 15/1/13 宜信 - 微佳 26 通道可以协调多个Goroutine的运行: package main import ( "fmt" ) func main() { name := "Robert" ch := make(chan byte, 1) go func() { fmt.Printf("Hello, %s.\n", name) ch <- 1 }() <-ch } 另一个可选方案是 : 使用 sync.WaitGroup Channel(9) 15/1/13 宜信 - 微佳 27 单向 Channel: type IntChan <-chan int func asyncReceive(ch <-IntChan) { // } type IntChan chan<- int func asyncSend() <-chan int { // } Channel(10) 15/1/13 宜信 - 微佳 28 Channel 与 for语句: var ch = make(chan int, 10) // 省略若干条语句 if ch != nil { for e := range ch { fmt.Printf("Element: %v\n", e) } } 如此从通道中接收值的优势是在通道关闭后 for循环会自动退出。 Channel(11) 15/1/13 宜信 - 微佳 29 Channel 与 select语句: var ch1 = make(chan int, 10) var ch2 = make(chan string, 10) // 省略若干条语句 select { case e1 := <-ch1: fmt.Printf("1th case is selected. e1=%v.\n", e1) case e2 := <-ch2: fmt.Printf("2th case is selected. e2=%v.\n", e2) default: fmt.Println("default!") } 并发编程原理一窥(1) 15/1/13 宜信 - 微佳 30 三个核心元素: M P G 互相引用 1:N ² M:Machine的缩写。一个M代表 了一个内核线程。 ² P:Processor的缩写。一个P代表 了M所需的上下文环境。 ² G:Goroutine的缩写。一个G代表 了对一段需要被并发执行的Go语 言代码的封装。 并发编程原理一窥(2) 15/1/13 宜信 - 微佳 31 M、P、G 与 KSE: ² KSE:Kernel Scheduled Entities 的缩写,即:内核调度实体。 KSE M P P G G G G G G G G 并发编程原理一窥(3) 15/1/13 宜信 - 微佳 32 G 的状态转换: Gidle Grunnable Grunning Gdead 新建 初始化 开始运行 运行完成 Gsyscall 不可直接运行 可直接 运行 进入 系统 调用 Gwaiting 等待 事件 程序结束 重新初始化 事件 到来 退出系统 调用 并发编程原理一窥(4) 15/1/13 宜信 - 微佳 33 变更P的最大数量: 都是 否 结束 开始 重新初始化全局P列表 获取旧值 抛出异常 重新分配所有可运行的G 废弃多余的P 进行最后的准备工作 变更P最大数量的值 旧值和新值 是否合法 这会使调度工作 发生短暂的停顿 并发编程原理一窥(5) 15/1/13 宜信 - 微佳 34 调度器跟踪: Ø  方法:设置环境变量︎︎︎GODEBUG Ø  值的选项: ü  打印简要信息:schedtrace=N (每N毫秒打印一次简要信息) ü  打印详细信息:schedtrace=N,scheddetail=1 (每N毫秒打印一次详细信息) 一些控制参数(1) 15/1/13 宜信 - 微佳 35 P的最大数量: Ø  设定方法: ü  调用函数 runtime.GOMAXPROCS ü  设置环境变量 GOMAXPROCS Ø  备注: ²  默认数量是1,认可的数量最大为256 ²  尽早设置,以避免对程序性能带来影响 一些控制参数(2) 15/1/13 宜信 - 微佳 36 M的最大数量: Ø  设定方法: ü  调用函数 runtime/debug.SetMaxThreads Ø  备注: ²  默认数量是10000 ²  过小的参数值会引发运行时恐慌! ²  当M的实际数量大于设定值时也会引发运行时恐慌! 一些控制参数(3) 15/1/13 宜信 - 微佳 37 G栈空间的最大字节数量: Ø  设定方法: ü  调用函数 runtime/debug.SetMaxStack Ø  备注: ²  当此数量大于设定值时会引发运行时恐慌! ²  正因为如此,要避免设置过小的数值 ²  同时也要避免设置过大的数值(你懂的) 一些控制参数(4) 15/1/13 宜信 - 微佳 38 手动调度G : Ø  使当前G让出CPU: ü  调用函数 runtime.Gosched ü  备注:只能使其暂停运行,且无法精确预知恢复时间 Ø  结束当前G的运行: ü  调用函数 runtime.Goexit ü  备注:仍会保证相关defer语句的执行完成 一些控制参数(5) 15/1/13 宜信 - 微佳 39 锁定当前G和当前M : Ø  锁定方法: ü  调用函数 runtime.LockOSThread Ø  备注: ²  多次调用不会有副作用,但仅有最后一次调用会生效 ²  解锁方法:调用函数 runtime.UnlockOSThread 一些控制参数(6) 15/1/13 宜信 - 微佳 40 与GC相关 : Ø  设定垃圾收集比率: ü  调用函数 runtime/debug.SetGCPercent ü  设置环境变量 GOGC Ø  手动操作: ²  runtime.GC:手动垃圾收集 ²  runtime/debug.FreeOSMemory:手动垃圾收集和清扫 推荐阅读列表 15/1/13 宜信 - 微佳 41 【仅限Go并发编程方面】 l  英文资料 ü  Go语言源码 ü  Golang Nuts(Google Groups) ü  Share Memory By Communicating - The Go Blog ü  Go Concurrency Patterns ü  Advanced Go Concurrency Patterns l  中文资料 ü  《Go并发编程实战》 Q & A Let’s Go! 15/1/13 宜信 - 微佳 42
还剩41页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 3 人已下载

下载pdf

pdf贡献者

dwe6b

贡献于2015-01-14

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf