文章目录
学习本次课程需要对Go语言有一定的了解,推荐Google官方的Go教程 A Tour of Go
线程
多个线程允许一个程序同时进行多项任务,每个线程内部程序串行运行,并且有自己的程序计数器、寄存器和栈空间。
使用线程的优点
多线程的应用在分布式系统中非常常见,因为它能够支持并发操作,非常契合分布式系统的特点。
- I/O concurrency
– 使用线程可以同时处理大量的I/O任务。一种常见的场景是,client
构建多个线程来向不同的server
发起rpc
请求,每个请求线程得到响应后再执行对应的处理任务。 - Multicore performance
– 使用多线程可以最大限度的利用多核CPU的性能。多个线程可以由不同的CPU核心进行处理,不同CPU核心的线程拥有独立的CPU周期。 - Convenience
– 很多时候多线程可以大大简化我们的编程难度。比如在分布式系统中,我们想要每隔一定的时间进行一次事件检查(如 MapReduce中Master节点检查Worker是否异常),我们就可以创建一个线程,让其专门负责定期检查Worker是否存活。
事件驱动编程
如果要实现并发I/O,除了采取多线程的方式,还可以采用事件驱动编程的思想来实现,如epoll
模型等。在事件驱动编程中,有一个线程会负责循环检测所有的事件状态,如客户端发起的rpc
请求等,当该线程检测到事件到来时,如服务器响应rpc
请求,该线程就会调用相应的处理函数,并继续进行循环监听。事件驱动编程相比多线程的实现方式有以下不同:
- 优点
– 开销更小(多线程的创建和删除以及空间占用远大于事件驱动) - 缺点
– 无法充分利用多核CPU的性能
– 实现较为复杂
线程中的挑战
在进行多线程编程时,通常需要仔细考虑以下几个重要问题。
- shared data
– 线程间是可以共享进程数据的,但是在使用共享数据的过程中,很可能会出现冲突问题。如两个线程同时执行n=n+1
,由于读写的先后顺序不一致,程序产生的结果也会不一样。 - coordination
– 我们经常需要线程间能够相互协作,比如经典的消费者-生产者模型。在Go
语言中,线程间的相互协作通常有以下几种实现方式,channel
,sync.Cond
和sync.WaitGroup
。 - deadlock
Example: web crawler
下面用一个简单的网页爬虫来展示Go
中多线程的应用,对于一个网页爬虫,我们需要其从给定的url
出发不断递归查询,并且每个url
只能爬取一次。我们首先先给出基本的数据结构。
//
// main
//
func main() {
fmt.Printf("=== Serial===\n")
Serial("http://golang.org/", fetcher, make(map[string]bool))
fmt.Printf("=== ConcurrentMutex ===\n")
ConcurrentMutex("http://golang.org/", fetcher, makeState())
fmt.Printf("=== ConcurrentChannel ===\n")
ConcurrentChannel("http://golang.org/", fetcher)
}
//
// Fetcher
//
type Fetcher interface {
// Fetch returns a slice of URLs found on the page.
Fetch(url string) (urls []string, err error)
}
// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
body string
urls []string
}
func (f fakeFetcher) Fetch(url string) ([]string, error) {
if res, ok := f[url]; ok {
fmt.Printf("found: %s\n", url)
return res.urls, nil
}
fmt.Printf("missing: %s\n", url)
return nil, fmt.Errorf("not found: %s", url)
}
// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
"http://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"http://golang.org/pkg/",
"http://golang.org/cmd/",
},
},
"http://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"http://golang.org/"