本节内容:Lect 2   RPC and Threads


线程:Threads allow one program to (logically) execute many things at once.
The threads share memory. However, each thread includes some per-thread state: program counter, registers, stack.

下面以go语言写一个爬虫作为例子来介绍线程:

Go example: crawler.go

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. // Several solutions to the crawler exercise from the Go tutorial: https://tour.golang.org/concurrency/10
  7.  
  8. type fakeResult struct {
  9. body string
  10. urls []string
  11. }
  12.  
  13. // fakeFetcher is Fetcher that returns canned results.
  14. type fakeFetcher map[string]*fakeResult
  15.  
  16. // fetcher is a populated fakeFetcher.
  17. var fetcher = fakeFetcher{
  18. "http://golang.org/": &fakeResult{
  19. "Title: The Go Programming Language",
  20. []string{
  21. "http://golang.org/pkg/",
  22. "http://golang.org/cmd/",
  23. },
  24. },
  25. "http://golang.org/pkg/": &fakeResult{
  26. "Title: Packages",
  27. []string{
  28. "http://golang.org/",
  29. "http://golang.org/cmd/",
  30. "http://golang.org/pkg/fmt/",
  31. "http://golang.org/pkg/os/",
  32. },
  33. },
  34. "http://golang.org/pkg/fmt/": &fakeResult{
  35. "Title: Package fmt",
  36. []string{
  37. "http://golang.org/",
  38. "http://golang.org/pkg/",
  39. },
  40. },
  41. "http://golang.org/pkg/os/": &fakeResult{
  42. "Title: Package os",
  43. []string{
  44. "http://golang.org/",
  45. "http://golang.org/pkg/",
  46. },
  47. },
  48. }
  49.  
  50. type Fetcher interface {
  51. Fetch(urlstring string) (urllist []string, err error)
  52. // Fetch(urlstring) method returns a slice of URLs found on the page.
  53. }
  54.  
  55. func (f fakeFetcher) Fetch(urlstring string) ([]string, error) {
  56. if res, ok := f[urlstring]; ok {
  57. //https://tour.golang.org/flowcontrol/6
  58. fmt.Printf("found: %s\n", urlstring)
  59. return res.urls, nil
  60. }
  61. fmt.Printf("missing: %s\n", urlstring)
  62. return nil, fmt.Errorf("not found: %s", urlstring)
  63. }
  64.  
  65. // ###### Serial crawler ######
  66.  
  67. func Serial(url string, fetcher Fetcher, fetched map[string]bool) {
  68. if fetched[url] {
  69. return
  70. }
  71. fetched[url] = true
  72. urls, err := fetcher.Fetch(url)
  73. if err != nil {
  74. return
  75. }
  76. for _, u := range urls {
  77. Serial(u, fetcher, fetched)
  78. }
  79. return
  80. }
  81.  
  82. // ###### Concurrent crawler with shared state and Mutex ######
  83.  
  84. func makeState() *fetchState {
  85. f := &fetchState{}
  86. f.fetched = make(map[string]bool)
  87. return f
  88. }
  89.  
  90. type fetchState struct {
  91. mu sync.Mutex
  92. fetched map[string]bool
  93. }
  94.  
  95. func ConcurrentMutex(url string, fetcher Fetcher, f *fetchState) {
  96. f.mu.Lock()
  97. if f.fetched[url] {
  98. f.mu.Unlock()
  99. return
  100. }
  101. f.fetched[url] = true
  102. f.mu.Unlock()
  103.  
  104. urls, err := fetcher.Fetch(url)
  105. if err != nil {
  106. return
  107. }
  108. var done sync.WaitGroup
  109. for _, u := range urls {
  110. done.Add(1)
  111. go func(u string) {
  112. defer done.Done()
  113. ConcurrentMutex(u, fetcher, f)
  114. }(u)
  115. }
  116. done.Wait()
  117. return
  118. }
  119.  
  120. // ###### Concurrent crawler with channels ######
  121.  
  122. func worker(url string, ch chan []string, fetcher Fetcher) {
  123. urls, err := fetcher.Fetch(url)
  124. if err != nil {
  125. ch <- []string{}
  126. } else {
  127. ch <- urls
  128. }
  129. }
  130.  
  131. func master(ch chan []string, fetcher Fetcher) {
  132. n := 1
  133. fetched := make(map[string]bool)
  134. for urls := range ch {
  135. for _, u := range urls {
  136. if fetched[u] == false {
  137. fetched[u] = true
  138. n += 1
  139. go worker(u, ch, fetcher)
  140. }
  141. }
  142. n -= 1
  143. if n == 0 {
  144. break
  145. }
  146. }
  147. }
  148.  
  149. func ConcurrentChannel(url string, fetcher Fetcher) {
  150. ch := make(chan []string)
  151. go func() {
  152. ch <- []string{url}
  153. }()
  154. master(ch, fetcher)
  155. }
  156.  
  157. // ###### main ######
  158.  
  159. func main() {
  160. fmt.Printf("=== Serial===\n")
  161. Serial("http://golang.org/", fetcher, make(map[string]bool)) //Serial version of crawler
  162.  
  163. fmt.Printf("=== ConcurrentMutex ===\n")
  164. ConcurrentMutex("http://golang.org/", fetcher, makeState())
  165.  
  166. fmt.Printf("=== ConcurrentChannel ===\n")
  167. ConcurrentChannel("http://golang.org/", fetcher)
  168. }

为了简便起见,这其实只是一个假的爬虫......并没有涉及网络访问,它的作用就是在fetcher中建立一个string->fakeResult类型的hash table,表示每个网页上的链接列表,并通过爬虫函数读取它们。为了演示go语言的并发,代码中实现了三种函数:Serial,ConcurrentMutex,ConcurrentChannel

在这段代码中,首先定义了一个接口Fetcher(go中接口的概念和java相似),其中有一个方法Fetch,用于在fetcher中返回urlstring所对应的链接列表。和java不一样,go语言中方法和函数不是一个概念:方法是面向对象中的概念。go中方法和函数最大的区别就是方法带有一个接收器(Fetch()中的f fakeFetcher参数),表示调用f对象的Fetch()方法(用法即some_obj_f.Fetch(url),这样就可以自动适配不同对象的同名方法;而函数是面向过程中的概念,函数只有输入参数和输出参数,和对象无关。

在58行这里的if有个神奇的用法,参考 https://tour.golang.org/flowcontrol/6

接下来我们先来看serial的版本。它的输入参数包括根域名url,fetcher(前面提到过的hash table),和一个bool数组fetched(用来记录哪些网站被访问过了)。注意163行这里有个神奇的用法make(),参考https://www.jianshu.com/p/f01841004810。  serial函数本身比较简单,就不赘述了,基本思路就是对fetcher中的每个域名,递归抓取它下面的链接(在fakeResult里面)。

第二个版本是ConcurrentMutex

  • 注意它的输入参数fetchState,里面除了bool数组之外还多了一个互斥锁mu。它的原理就是用来给共享变量fetched加锁,保证在多线程爬虫时,每次只有一个线程能访问fetched变量。当mu已经被锁上时,任何试图访问它的线程都会阻塞在mu.Lock()处,直到mu被释放掉才能往下进行(可以理解为二元信号量的wait操作)。而对于每个域名下面的链接,会再启动一个ConcurrentMutex线程来抓取,而不是单纯的递归,这样就实现了多线程。  
  • 另外110行有一个var done sync.WaitGroup,这个是用来确定何时结束爬虫的,WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器地值减为0 (可以理解为counting semaphore)[参考https://studygolang.com/articles/12972?fr=sidebar]。  
  • 注意113-116行有一个匿名函go func,这个是go中的协程(go routine),作用有点像c里面的fork(),可以理解为新开了一个线程。函数里面的语句会在一个新建的go routine里执行。这样就实现了并发访问多个url
  • 114行有个defer done.Done(),defer关键字的含义是:defer后面的函数在defer语句所在的函数执行结束的时候会被调用。这里也就是func函数运行结束后(ConcurrentMutex之后)把计数器-1。       另外用defer和不用defer的一大不同点就是,defer后面紧跟的函数值和函数参数会立即被求值(函数体会立即执行),但函数不会立即调用(不会立即被return),本例中还看不出来这一点,可以参考https://www.cnblogs.com/racaljk/p/8150726.htmlhttps://www.cnblogs.com/racaljk/p/8150726.html
  • 这个版本虽然实现了一定程度上的并发,但是对fetched的访问仍然是serial的。如果其中发生了很多的race,那么整体速度就被拖慢了。

第三个版本是ConcurrentChannel,这个例子中用了Go channel。这部分可以参考https://www.cnblogs.com/pdev/p/11286349.html

When to use sharing and locks, versus channels?

  • Most problems can be solved in either style
  • What makes the most sense depends on how the programmer thinks
    • state -- sharing and locks
    • communication -- channels
    • waiting for events -- channels
  • Use Go's race detector:
    • https://golang.org/doc/articles/race_detector.html
    • go test -race

RPC

基本概念5105都学过了.....这里来看看用go语言如何实现吧。

在5105课上讲过Reliable RPC的概念,讲的是如果在server-client之间如果传输出了故障该怎么办。

  1. 17_reliable_comm
  2. . Reliable RPC: client-server

  3. . Server failure client 不知道 server 啥时候挂的,是 operation 执行前还是执行后)
  4. Sol: 分三种 operation semantics:
  5. Exactly once(保证操作恰好执行一次): impossible to achieve
  6. At least once(至少执行过一次): retry
  7. At most once(执行过 次或 次): send request only once
  8.  
  9. . Client failure client 已经挂了。 server 没必要再执行了,浪费资源)
  10. Sol: Extermination: log at client stub and explicitly kill orphans
    / Reincarnation: Divide time into epochs between failures and delete computations from old epochs.
    / Expiration: give each RPC a fixed quantum T. Explicitly request extensions.

At least once适用于以下场景:If it's OK to repeat operations (e.g. read-only op), or if application has its own plan for coping w/ duplicates (which you will need for Lab 1)

at most once的思路是,server RPC code could detect duplicate requests, and returns previous reply instead of re-running the handler(RPC function).  在Lab2中就会用到这个方法。

Q: how to detect a duplicate request?
  A: client includes unique ID (XID) when sending each request, and uses the same XID for re-send
      server:
      if seen[xid]:
          r = old[xid]
      else
          r = handler()
          old[xid] = r
          seen[xid] = true
但是at most once也有个问题:如果server挂了,导致seen[]丢失了,那么server就不知道哪个xid曾经接收过了。

exactly once需要在at most once的基础上增加容错协议。这个会在Lab3中用到。

Go RPC is "at-most-once"
  STEP1  open TCP connection
  STEP2  write request to TCP connection
  STEP3  TCP may retransmit, but server's TCP will filter out duplicates

There is no retry in Go code (i.e. will NOT create 2nd TCP connection)
    Go RPC code returns an error if it doesn't get a reply, when
          perhaps after a timeout (from TCP)
          perhaps server didn't see request
          perhaps server processed request but server/net failed before reply came back

下面以go语言写的简易key-value storage为例:

Go example: kv.go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net"
  6. "net/rpc"
  7. "sync"
  8. )
  9.  
  10. // RPC request/reply definitions
  11.  
  12. const (
  13. OK = "OK"
  14. ErrNoKey = "ErrNoKey"
  15. )
  16.  
  17. type Err string
  18.  
  19. type PutArgs struct {
  20. Key string
  21. Value string
  22. }
  23.  
  24. type PutReply struct {
  25. Err Err
  26. }
  27.  
  28. type GetArgs struct {
  29. Key string
  30. }
  31.  
  32. type GetReply struct {
  33. Err Err
  34. Value string
  35. }
  36.  
  37. // Client -------------------------------------------------------
  38.  
  39. func connect() *rpc.Client { //建立与server的连接
  40. client, err := rpc.Dial("tcp", "127.0.0.1:1234")
  41. if err != nil {
  42. log.Fatal("dialing:", err)
  43. }
  44. return client
  45. }
  46.  
  47. func get(key string) string {
  48. client := connect()
  49. args := GetArgs{"subject"}
  50. reply := GetReply{}
  51. err := client.Call("KV.Get", &args, &reply) //rpc调用server上的函数
  52. if err != nil {
  53. log.Fatal("error:", err)
  54. }
  55. client.Close() //关闭连接
  56. return reply.Value
  57. }
  58.  
  59. func put(key string, val string) {
  60. client := connect()
  61. args := PutArgs{"subject", "6.824"}
  62. reply := PutReply{}
  63. err := client.Call("KV.Put", &args, &reply)
  64. if err != nil {
  65. log.Fatal("error:", err)
  66. }
  67. client.Close()
  68. }
  69.  
  70. // Server -------------------------------------------------------
  71.  
  72. type KV struct {
  73. mu sync.Mutex //手动为数据区设置一个锁
  74. data map[string]string
  75. }
  76.  
  77. func server() { //建立server
  78. kv := new(KV)
  79. kv.data = map[string]string{}
  80. rpcs := rpc.NewServer()
  81. rpcs.Register(kv)
  82. l, e := net.Listen("tcp", ":1234")
  83. if e != nil {
  84. log.Fatal("listen error:", e)
  85. }
  86. go func() {
  87. for {
  88. conn, err := l.Accept()
  89. if err == nil {
  90. go rpcs.ServeConn(conn)
  91. } else {
  92. break
  93. }
  94. }
  95. l.Close()
  96. }()
  97. }
  98.  
  99. func (kv *KV) Get(args *GetArgs, reply *GetReply) error {
  100. kv.mu.Lock()
  101. defer kv.mu.Unlock()
  102.  
  103. val, ok := kv.data[args.Key]
  104. if ok {
  105. reply.Err = OK
  106. reply.Value = val
  107. } else {
  108. reply.Err = ErrNoKey
  109. reply.Value = ""
  110. }
  111. return nil
  112. }
  113.  
  114. func (kv *KV) Put(args *PutArgs, reply *PutReply) error {
  115. kv.mu.Lock()
  116. defer kv.mu.Unlock()
  117.  
  118. kv.data[args.Key] = args.Value
  119. reply.Err = OK
  120. return nil
  121. }
  122.  
  123. // main -------------------------------------------------------
  124.  
  125. func main() {
  126. server()
  127.  
  128. put("subject", "6.824")
  129. fmt.Printf("Put(subject, 6.824) done\n")
  130. fmt.Printf("get(subject) -> %s\n", get("subject"))
  131. }

逻辑还是比较简单的...比java thrift简洁多了。


Ref:

https://golang.org/doc/effective_go.html

https://golang.org/pkg/net/rpc/

https://tour.golang.org/concurrency/10

https://www.cnblogs.com/pdev/p/10936485.html

MIT 6.824学习笔记2 RPC/Thread的更多相关文章

  1. MIT 6.824学习笔记4 Lab1

    现在我们准备做第一个作业Lab1啦 wjk大神也在做6.824,可以参考大神的笔记https://github.com/zzzyyyxxxmmm/MIT6824_Distribute_System P ...

  2. MIT 6.824学习笔记3 Go语言并发解析

    之前看过一个go语言并发的介绍:https://www.cnblogs.com/pdev/p/10936485.html   但这个太简略啦.下面看点深入的 还记得https://www.cnblog ...

  3. MIT 6.824学习笔记1 MapReduce

    本节内容:Lect 1 MapReduce框架的执行过程: master分发任务,把map任务和reduce任务分发下去 map worker读取输入,进行map计算写入本地临时文件 map任务完成通 ...

  4. RESTful源码学习笔记之RPC和 RESTful 什么区别

    REST,即Representational State Transfer的缩写.翻译过来是表现层状态转换.如果一个架构符合REST原则,就称它为RESTful架构.啥叫json-rpc?接口调用通常 ...

  5. RESTful源码学习笔记之RPC和Restful深入理解

    以下资料搜集自网络 0x00 RPC RPC 即远程过程调用(Remote Procedure Call Protocol,简称RPC),像调用本地服务(方法)一样调用服务器的服务(方法).通常的实现 ...

  6. rabbitMQ学习笔记(七) RPC 远程过程调用

    关于RPC的介绍请参考百度百科里的关于RPC的介绍:http://baike.baidu.com/view/32726.htm#sub32726 现在来看看Rabbitmq中RPC吧!RPC的工作示意 ...

  7. Swoft2.x 小白学习笔记 (四) --- RPC

    介绍 swoft 中 RPC使用:搭建访问服务端和客户端 RPC服务端: 一.配置,在文件 /app/bean.php中添加 return [ 'rpcServer' => [ 'class' ...

  8. RabbitMQ学习笔记(六) RPC

    什么RPC? 这一段是从度娘摘抄的. RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的 ...

  9. Hadoop源码学习笔记(4) ——Socket到RPC调用

    Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要 ...

随机推荐

  1. python实现通过企业微信发送消息

    实现了通过企业微信发送消息,平时用于运维的告警还是不错的,相对于邮件来说,实时性更高,不过就是企业微信比较麻烦,此处不做过多解释. 企业微信api的详细请看:http://work.weixin.qq ...

  2. python常用函数 U

    update(dict) 字典合并,生成的为新的字典,新字典操作不会影响老字典. 例子:

  3. Dubbo学习-6-springboot整合dubbo

    1.在前面帖子和工程的基础上,这里使用springboot整合dubbo,首先创建springboot项目: https://start.spring.io/  进入spring Initializr ...

  4. swift中为什么要创造出可选型?

    (1)因为nil这个东西,swift中没有就是没有.  Int? 叫 整型可选型,如果不提前声明,直接赋值变量 nil会报错 . 可以将Int赋值给Int?   ,但是不能将Int?赋值给Int . ...

  5. KindEditor 完全复制word内容

    Chrome+IE默认支持粘贴剪切板中的图片,但是我要发布的文章存在word里面,图片多达数十张,我总不能一张一张复制吧?Chrome高版本提供了可以将单张图片转换在BASE64字符串的功能.但是无法 ...

  6. POJ 1502 MPI MaeIstrom ( 裸最短路 || atoi系统函数 )

    题意 : 给出 N 个点,各个点之间的路径长度用给出的下三角矩阵表示,上上角矩阵和下三角矩阵是一样的,主对角线的元素都是 0 代表自己到达自己不用花费,现在问你从 1 到 N 的最短路,矩阵的 x 代 ...

  7. [NOIP2016][luogu]换教室[DP]

    [NOIP2016] Day1 T3 换教室 ——!x^n+y^n=z^n 题目描述 对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程. 在可以选择的课程中,有 2n 节课程 ...

  8. [NOIP2015]运输计划 题解

    题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条 航道连通了 L 国的所有星球. 小 P 掌管一 ...

  9. VBox 启动虚拟机失败 - NtCreateFile(\Device\VBoxDrvStub)

    在Vbox(5.0.8 版本)启动Ubuntu的虚拟机时,遇到错误信息: NtCreateFile(\Device\VBoxDrvStub) failed: 0xc000000034 STATUS_O ...

  10. PHP 调试 - Xdebug

    PHP 调试指南.pdf PHP 程序员的调试技术 根据要调试的对象的不同,采取的方法也不一样: 调试 web 应用:对于 web 应用,可以在浏览器中安装插件,或者在 IDE 中设置,下面的设置二选 ...