阐明

本系列文章是对赫赫有名的 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、指针等各种数据格式为能够传输的格局

【未完待续。。。】