本节内容: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

package main
import (
"fmt"
"sync"
)
// Several solutions to the crawler exercise from the Go tutorial: https://tour.golang.org/concurrency/10 type fakeResult struct {
body string
urls []string
} // fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult // fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
"http://golang.org/": &fakeResult{
"Title: The Go Programming Language",
[]string{
"http://golang.org/pkg/",
"http://golang.org/cmd/",
},
},
"http://golang.org/pkg/": &fakeResult{
"Title: Packages",
[]string{
"http://golang.org/",
"http://golang.org/cmd/",
"http://golang.org/pkg/fmt/",
"http://golang.org/pkg/os/",
},
},
"http://golang.org/pkg/fmt/": &fakeResult{
"Title: Package fmt",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
"http://golang.org/pkg/os/": &fakeResult{
"Title: Package os",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
} type Fetcher interface {
Fetch(urlstring string) (urllist []string, err error)
// Fetch(urlstring) method returns a slice of URLs found on the page.
} func (f fakeFetcher) Fetch(urlstring string) ([]string, error) {
if res, ok := f[urlstring]; ok {
//https://tour.golang.org/flowcontrol/6
fmt.Printf("found: %s\n", urlstring)
return res.urls, nil
}
fmt.Printf("missing: %s\n", urlstring)
return nil, fmt.Errorf("not found: %s", urlstring)
} // ###### Serial crawler ###### func Serial(url string, fetcher Fetcher, fetched map[string]bool) {
if fetched[url] {
return
}
fetched[url] = true
urls, err := fetcher.Fetch(url)
if err != nil {
return
}
for _, u := range urls {
Serial(u, fetcher, fetched)
}
return
} // ###### Concurrent crawler with shared state and Mutex ###### func makeState() *fetchState {
f := &fetchState{}
f.fetched = make(map[string]bool)
return f
} type fetchState struct {
mu sync.Mutex
fetched map[string]bool
} func ConcurrentMutex(url string, fetcher Fetcher, f *fetchState) {
f.mu.Lock()
if f.fetched[url] {
f.mu.Unlock()
return
}
f.fetched[url] = true
f.mu.Unlock() urls, err := fetcher.Fetch(url)
if err != nil {
return
}
var done sync.WaitGroup
for _, u := range urls {
done.Add(1)
go func(u string) {
defer done.Done()
ConcurrentMutex(u, fetcher, f)
}(u)
}
done.Wait()
return
} // ###### Concurrent crawler with channels ###### func worker(url string, ch chan []string, fetcher Fetcher) {
urls, err := fetcher.Fetch(url)
if err != nil {
ch <- []string{}
} else {
ch <- urls
}
} func master(ch chan []string, fetcher Fetcher) {
n := 1
fetched := make(map[string]bool)
for urls := range ch {
for _, u := range urls {
if fetched[u] == false {
fetched[u] = true
n += 1
go worker(u, ch, fetcher)
}
}
n -= 1
if n == 0 {
break
}
}
} func ConcurrentChannel(url string, fetcher Fetcher) {
ch := make(chan []string)
go func() {
ch <- []string{url}
}()
master(ch, fetcher)
} // ###### main ###### func main() {
fmt.Printf("=== Serial===\n")
Serial("http://golang.org/", fetcher, make(map[string]bool)) //Serial version of crawler fmt.Printf("=== ConcurrentMutex ===\n")
ConcurrentMutex("http://golang.org/", fetcher, makeState()) fmt.Printf("=== ConcurrentChannel ===\n")
ConcurrentChannel("http://golang.org/", fetcher)
}

为了简便起见,这其实只是一个假的爬虫......并没有涉及网络访问,它的作用就是在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之间如果传输出了故障该怎么办。

17_reliable_comm
. Reliable RPC: client-server

. Server failure( client 不知道 server 啥时候挂的,是 operation 执行前还是执行后)
Sol: 分三种 operation semantics:
Exactly once(保证操作恰好执行一次): impossible to achieve
At least once(至少执行过一次): retry
At most once(执行过 次或 次): send request only once . Client failure( client 已经挂了。 server 没必要再执行了,浪费资源)
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

package main
import (
"fmt"
"log"
"net"
"net/rpc"
"sync"
) // RPC request/reply definitions const (
OK = "OK"
ErrNoKey = "ErrNoKey"
) type Err string type PutArgs struct {
Key string
Value string
} type PutReply struct {
Err Err
} type GetArgs struct {
Key string
} type GetReply struct {
Err Err
Value string
} // Client ------------------------------------------------------- func connect() *rpc.Client { //建立与server的连接
client, err := rpc.Dial("tcp", "127.0.0.1:1234")
if err != nil {
log.Fatal("dialing:", err)
}
return client
} func get(key string) string {
client := connect()
args := GetArgs{"subject"}
reply := GetReply{}
err := client.Call("KV.Get", &args, &reply) //rpc调用server上的函数
if err != nil {
log.Fatal("error:", err)
}
client.Close() //关闭连接
return reply.Value
} func put(key string, val string) {
client := connect()
args := PutArgs{"subject", "6.824"}
reply := PutReply{}
err := client.Call("KV.Put", &args, &reply)
if err != nil {
log.Fatal("error:", err)
}
client.Close()
} // Server ------------------------------------------------------- type KV struct {
mu sync.Mutex //手动为数据区设置一个锁
data map[string]string
} func server() { //建立server
kv := new(KV)
kv.data = map[string]string{}
rpcs := rpc.NewServer()
rpcs.Register(kv)
l, e := net.Listen("tcp", ":1234")
if e != nil {
log.Fatal("listen error:", e)
}
go func() {
for {
conn, err := l.Accept()
if err == nil {
go rpcs.ServeConn(conn)
} else {
break
}
}
l.Close()
}()
} func (kv *KV) Get(args *GetArgs, reply *GetReply) error {
kv.mu.Lock()
defer kv.mu.Unlock() val, ok := kv.data[args.Key]
if ok {
reply.Err = OK
reply.Value = val
} else {
reply.Err = ErrNoKey
reply.Value = ""
}
return nil
} func (kv *KV) Put(args *PutArgs, reply *PutReply) error {
kv.mu.Lock()
defer kv.mu.Unlock() kv.data[args.Key] = args.Value
reply.Err = OK
return nil
} // main ------------------------------------------------------- func main() {
server() put("subject", "6.824")
fmt.Printf("Put(subject, 6.824) done\n")
fmt.Printf("get(subject) -> %s\n", get("subject"))
}

逻辑还是比较简单的...比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. Redux中间件之redux-thunk使用详解

    Redux的核心概念其实很简单:将需要修改的state都存入到store里,发起一个action用来描述发生了什么,用reducers描述action如何改变state tree .创建store的时 ...

  2. alert(1) to win 2

    function escape(s) { s = s.replace(/"/g, '\\"'); return '<script>console.log("' ...

  3. 英国已有500万宽带用户接入并开始使用IPv6技术

    2018年英国首家为客户提供IPv6的主要ISP.随着所有现有的符合条件的用户线路启用,约90%的固定宽带用户群接入并开始使用IPv6,为IPv6互联网增加了超过500万个新眼球. 英国IPv6项目于 ...

  4. BZOJ 5046 分糖果游戏

    网页崩溃了 心态也崩溃了 MD劳资写了那么多 题意: 有a,b两个人分糖,每个人都有一个能量值.每个人每一轮可以选择进行两种操作: 1.取走最左边的糖果,补充相应的能量值并获取相应的美味度. 2.跳过 ...

  5. Java缓冲流写出数据实例

    public class BufferedWriterDemo throws IOException { public static void main(String[] args) throws I ...

  6. ht-5 treemap特性

    (1)TreeMap类通过使用红黑树实现Map接口 (2)TreeMap提供按排序顺序存储键值对的有效手段,同时允许快速检索 (3)不同于散列映射,树映射保证它的元素按键的自然顺序升序排列 (4)Tr ...

  7. 06 IntelliJ IDEA构建多模块项目

  8. 无法启用web调试服务器

    场景:vs2005   webservice 项目属性中:web : 使用IIS web  服务器  http://localhost/WSMA   --- >自动创建虚拟目录 点击调试的时候F ...

  9. sql2000如何完美压缩.mdf文件

    --收缩数据库 dbcc shrinkdatabase('test_db') , ) --数据文件mdf , ) --日志文件ldf ) --更新

  10. ResquestInfoServlet类通过访问HttpServletRequest对象的各种方法来读取HTTP请求中的特定信息,并且把它们写入到HTML中

    ResquestInfoServlet类通过访问HttpServletRequest对象的各种方法来读取HTTP请求中的特定信息,并且把它们写入到HTML中 ResquestInfoServlet.j ...