阐明
本系列文章是对赫赫有名的 MIT6.824 分布式系统课程 的翻译补充和学习总结,算是本人一边学习一边记录了。
如有疏漏谬误,还请斧正:)
继续更新 ing。。。
翻译 & 补充
内容
Go 中的线程和 RPC,看一下试验
为什么抉择 Go?
对线程良好的反对
不便的 RPC
类型平安
垃圾回收(没有内存开释后再被应用的问题)
线程 + 垃圾回收十分棒
绝对简略
在教程学习后,请浏览 effective go
线程
很有用的结构工具,然而有时候会很辣手
在 Go 里叫做“goroutines”;其余中央叫做线程
线程 =“执行线程”
线程容许一个程序同时做很多事件
每一个线程依序执行,就像没有线程的一般程序一样
线程之间共享内存
每个线程包含本人的状态:程序计数器,寄存器和栈
为什么抉择线程?
它们可能解决并发,在分布式系统中很有用
I/ O 并发
- 客户端并行地发送申请到许多服务器,并期待回复;
- 服务端解决多个客户端的申请,每个申请会阻塞;
- 在期待客户端 X 从磁盘读取数据的申请时,会解决客户端 Y 的申请。
多核的性能:在多个核上并行地执行代码
方便性:在后盾,每隔 1 秒,查看每个 worker 是否仍旧存活。
有没有线程的替代品?
有的:在一个独自的线程里,用代码明确地退出流动;通常称为“事件驱动”
维持每个流动的状态信息,比方每个客户端申请
一个“事件”的过程如下:
- 查看每个流动的新输出(比方,服务器的回应)
- 执行每个流动的下个步骤
- 更新状态
事件驱动实现 I / O 并发:
- 打消线程的耗费(可能会比拟大)
- 然而并没有应用到多核带来的减速
- 对代码来说很苦楚
线程带来的挑战:
共享数据:
- 比方,如果两个线程同时执行 n = n + 1 会怎么样?或者一个线程减少一个线程读数据?
- –> 应用锁(Go 里的 sync.Mutex)
- –> 防止共享会批改的数据
线程中的协同工作:
- 比方,一个线程生产数据,另一个线程生产。怎么让消费者期待(开释 CPU)?怎么让生产者告诉消费者?
- –> 应用 go 中的 channel,或 sync.Cond,或 WaitGroup
死锁:
- 加锁有环形依赖,或通信有环形依赖(比方:RPC 或 Go 的 channel)
让咱们看教程中的网络爬虫,把它作为一个线程示例
为什么是网络爬虫?
目前是获取所有的网页,比方:用于建设索引
网页和链接组成图
多个链接指向雷同的页面
图中有环形
爬虫带来的挑战:
须要应用 I / O 并发:
- 网络提早比网络容量更受限
- 须要同时获取多个 URL。为了减少每秒的 URL 获取数目,须要应用并发的多线程
每个 URL 仅“获取一次”
- 避免浪费网络带宽
- 对近程服务器更敌对
- 须要记住每个拜访过的 URL
晓得何时完结
咱们看下两种解决形式【课程表页面的 crawler.go】
Serial 爬虫:
通过递归 Serial 调用实现深度遍历
“fetched”map 防止了反复,突破环形;一个简略的 map,通过传递援用,调用者能够看到并调用者的更新
然而,一个时刻只能获取一个页面;咱们能够间接在 Serial()函数前加“go”吗?咱们能够试下,看看会产生什么?
ConcurrentMutex 爬虫:
创立一个线程用于获取每个页面,启动很多并发的获取,放慢获取速度
“go func”创立并执行协程,func 能够是匿名办法
多个线程共享“fetched”map,所以同一时刻只有一个线程能够获取任意的指定页面
为什么应用 Mutex(Lock()和 Unlock())?
-
起因一:
* 两个不同的 web 页面可能蕴含雷同的 URL 链接 * 两个线程可能会同步获取这两个页面 * T1 读取 fetched[url],T2 也读取一样内容 * 它们会同时发现这个 url 没有获取(already==false)* 它们会同时获取,这是谬误的 * 锁会让检查和更新操作原子化,所以只有一个线程看到 already==false
-
起因二:
* 从外部来看,map 是个简单的数据结构(tree?就还是可扩大的 hash?)* 并发更新会毁坏外部变量 * 并发读写会导致读失败
-
如果我正文掉 Lock()/Unlock(),会怎么样?
* go run crawler.go 会工作吗?* go run -race crawler.go 会检测出资源竞争,即便输入是正确的
ConcurrentMutex 爬虫怎么判断完结?
- sync.WaitGroup
- Wait()会期待所有的 Add()数目和 Done()始终,即,期待所有的子线程完结[图表:环形 URL 图上的协程树];每个树的节点上都有一个 WaitGroup
这个爬虫会创立多少个并发的线程?
ConcurrentChannel 爬虫:
一个 Go channel:
- 一个 channel 是一个对象,ch := make(chan int)
- 一个 channel 能够实现一个线程向另个一个线程发送数据,ch <- x,发送者期待,直到有协程接管,y := <- ch,for y := range ch,接受者期待,直到有协程发送
- channel 既反对通信又反对同步
- 多个线程能够在一个 channel 上通信
- channel 很便宜
- 记住:发送者会阻塞,直到接受者收到,“synchronnous”,小心死锁
ConcurrentChannel master()
- master() 创立一个 worker 协程用来获取每个页面
- worker() 发送页面的 URL 切片到 channel 上,多个 wroker 在同一个 channel 上发送
- master() 从 channel 上读取 URL 切片
什么状况下 master 在期待?master 期待的时候占用 CPU 工夫了吗?
不须要对 fetched map 加锁,因为它并没有被共享!
master 怎么晓得完结了?
- 放弃 worker 的数目在 n 以内
- 每个 worker 只在 channel 上发送一条数据
为什么多个线程应用雷同的 channel 不会资源竞争呢?
worker 线程写入 URL 切片,master 线程读取切片,却不应用锁,会有资源竞争吗?
- worker 只在发送“前”写入切片
- master 只在收到“后”读取切片
所以他们不会同时应用切片
什么时候应用共享和锁,什么时候应用 channel?
大多数问题用两种形式都能够解决
应用哪个取决于程序员的思考:
* 状态 -- 共享和锁
* 通信 -- channel
对于 6.824 的试验,倡议应用共享和锁存储状态,应用 sync.Cond 或 channel 或 time.Sleep()用来期待和告诉
近程过程调用(RPC)
分布式系统机制会要害的一块,所有试验都在应用 RPC
指标:客户端能和服务器的通信更易于编程
暗藏网络协议的细节
转换 string、数组、map、指针等各种数据格式为能够传输的格局
【未完待续。。。】