【MIT 6.824】LEC 2: RPC and Threads , go, 线程并发,channel 与常见问题

66 阅读2分钟

谈论一下Go语言,和接下来的lab中对分布式编程最有用的machinery。 Go内存安全,对线程、锁和同步有良好支持,有一个方便的RPC包。接下来的课程和程序中会经常用到RPC,用来让不同机器之间通信。相比之下C++中线程和内存回收问题极为复杂。

线程是管理并发的主要工具,Go中称为协程(Goroutine) ,Go中启动入口main函数本身就是一个协程。

使用协程的原因
  • 并发I/O
    Go每个线程可以通过RPC同时对网络上不同服务器发送请求和等待回复。

  • 多核并行
    利用好多核处理器。

  • 后台任务的便利性
    后台进行周期性任务,例如主服务器周期性检查worker是否存活。在之后的lab中会大量用到这个方式。

除了线程,还可以考虑事件驱动编程。它通常是一条线程和一个循环,循环等待可能会触发操作的事件,但是无法使用多核并行能力。而且通常来说,线程并行更方便,但是线程非常多时需要维护每个线程栈和调度表,开销会更大。

写线程代码时会遇到的挑战:

如何处理共享数据
线程共享地址空间,非原子操作并发时容易出错。此时就需要对非原子操作共享数据加锁
在编程过程中锁的问题(即线程race)也许不容易发现,Go中提供了race检测工具,在运行时加上 -race 参数即可。race检测的原理是跟踪线程近期读写内存位置,发现有不同线程读写同一位置且没有锁时会进行提醒。但是它会使用大量内存,且无法检测静态的未执行代码,使用时需要建立测试标准且让所有代码都执行。 线程协作
有时候需要线程之间进行交互,例如传输数据。
Go中的channel可以实现这一点。
Sync.conf()也是个好办法,即唤醒信号。
Sync.waitgroup适合启动已知数量的Goroutines。 线程死锁
互相等待。在锁设置不合理时容易出现。 image.png 实际使用中需要对线程数量进行限制,一种办法是创建固定大小的worker池(线程池),复用线程而不是每次创建一个新线程。