RPC与Thread笔记

  • 线程=“执行线程”

    • 线程允许一个程序同时做很多事
    • 每一个线程就像普通非线程程序一样,都是串行执行
    • 线程之间共享内存
    • 每个线程都包含每个线程的状态,所以它们有
      • 程序计数器,寄存器,堆栈,就绪挂起执行等状态
  • 线程的好处

    • I/O并发
      • 客户端并行的向多个服务器发送请求并且等待响应
      • 服务器端处理多个客户端请求;每个请求都可能阻塞
      • 当等待从磁盘读取客户机X的数据时,处理来自客户端Y的请求
    • 多核性能
      • 在多个核心上并行执行代码
    • 便捷性
      • 在后台,每秒检查一次每个worker是否还活着
  • 除了线程有可替代方案吗

    • 有:在单线程中编写明确交错活动的代码。通常称其为“事件驱动”

    • 保存每个活动的状态表。 eg:每个客户端的请求

    • One "event" loop that:
          checks for new input for each activity (e.g. arrival of reply from server),
          does the next step for each activity,
          updates state.
      
    • 事件驱动可以实现I/O并发,并且消除了线程的成本(可观),但无法获得多核的速度,并且编程很麻烦

  • 编写线程的挑战

    • 安全的共享数据
      • 如果两个线程同时执行n=n+1或者一个线程读取数据,另一个线程递增此数据。这就是所谓的竞争——通常是一个常见的错误,
        • 可以通过使用锁来避免
        • 可以刻意避免共享可变数据
    • 线程之间的协调
      • 一个线程在生成数据,另一个线程在消费数据(消费者如何等待(并释放CPU),生产者如何唤醒消费者)
        • 使用GO中的channels
        • sync.Cond or sync.WaitGroup
    • 死锁
      • 通过锁和通信进行循环(如RPC或者Go channels)
  • 最简单的故障处理方案:“尽最大努力交付”的RPC

    • Call() 等待响应一段时间
    • 如果没有回应重新发送请求
    • 这样做几次
    • 然后放弃并返回错误
  • “尽最大努力交付”对应用程序来说如何?

    • 一种特别糟糕的情况
      • 客户端执行:
        • put(“k”,10);
        • put(“k”,20);
        • 均成功(但网络延迟)
      • 当get(“k”)
        • [diagram, timeout, re-send, original arrives late]
  • 什么时候用“尽最大努力交付”

    • 只读操作
    • 重复执行无任何反作用的操作
      • eg:DB检查记录是否已被插入
  • 更好的RPC行为:“最多执行一次的”RPC

    • 理念:如果没有resp,客户端重新发送请求

      • 服务器端的RPC代码检测重复请求
      • 返回之前的回复,而不需要重新处理
    • 如何检测重复请求

      • 客户端每次请求时包含唯一性索引 ID(XID),再次发送时使用相同的XID

      • 服务器端:

        • server:
              if seen[xid]:
                r = old[xid]
              else
                r = handler()
                old[xid] = r
                seen[xid] = true
          
  • 如何避免庞大的seen[xid]表

    • 想法:
      • 每个客户端有一个唯一性ID(也许是一个大随机数),为每一个RPC分配递增的序列号
      • 客户端在每次RPC中包括“已看到所有回复<=X”
      • 就类似于TCP序列号和ACKs,或者只允许客户端同时有一个未完成的RPC
      • 这样服务器就能保持O(客户端数),而不是O(XID)
  • 服务器最终必须丢弃旧RPC或者旧客户端的信息,何时丢弃是安全的,当原始请求仍在执行时,如何处理重复请求

    • 为每一个正在执行的RPC设置“待处理”标志;等待或者忽略
  • 当线程并行执行的时候,如果内核数量少于可运行的线程数,运行时将抢先在线程之间分配内核

  • 单线程和多线程在程序内存地址空间的区别

  • 在这里插入图片描述

    • 如果两个线程在单核CPU上运行,当从运行一个线程 (T1) 切换到运行另一个线程 (T2) 时,必须进行上下文切换。线程之间的上下文切换与进程之间的上下文切换非常相似,因为在运行T2之前必须保存T1的寄存器状态并恢复T2的寄存器状态。对于进程,我们将状态保存到进程控制块(PCB);现在,我们需要一个或多个线程控制块(TCB)来存储进程的每个线程的状态。与进程相比,我们在线程之间执行的上下文切换有一个主要区别:地址空间保持不变(即不需要切换我们正在使用的页表)。
    • 线程和进程的另一个主要区别是堆栈,每个线程都有一个堆栈。假设我们有一个多线程进程,其中有两个线程;生成的地址空间看起来有所不同(图 26.1,右)。
    • 以前,堆栈和堆可以独立增长,只有当地址空间空间不足时才会出现问题。在这里,我们不再有这么好的情况了。幸运的是,这通常是可以的,因为堆栈通常不必很大。
  • 协程与线程的区别在于协程是完全在应用程序内 (低特权运行级) 实现的,不需要操作系统的支持,占用的资源通常也比操作系统线程更小一些。协程可以随时切换执行流的特性,用于实现状态机、actor model, goroutine 等。

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

poison_Program

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值