阐明
本系列文章是对赫赫有名的 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、指针等各种数据格式为能够传输的格局
【未完待续。。。】