11 February 2017

golang通过语言层面的特性goroutine和channel提供了优雅的并发编程方式,Share Memory By Communicating一文中有这么句“名言”:

Do not communicate by sharing memory; instead, share memory by communicating.

单纯看这句话其实不好理解,但结合文中的例子算是基本明白其中的含义了。

文中以编写并发拉取给定的URL资源程序为例子,展示了传统多线程并发编程模型和golang并发编程模型的不同。

传统多线程并发编程模型下,拉取程序代码大致如下:

type Resource struct {
    url        string // 需要拉取的资源的URL
    polling    bool   // 是否正在拉取
    lastPolled int64  // 最新的拉取完成时间
}

type Resources struct {
    data []*Resource // 待拉取资源列表
    lock *sync.Mutex // 同步锁
}

func Poller(res *Resources) {
    for {
        // get the least recently-polled Resource
        // and mark it as being polled
        res.lock.Lock()
        var r *Resource
        for _, v := range res.data {
            if v.polling {
                continue
            }
            if r == nil || v.lastPolled < r.lastPolled {
                r = v
            }
        }
        if r != nil {
            r.polling = true
        }
        res.lock.Unlock()
        if r == nil {
            continue
        }

        // poll the URL

        // update the Resource's polling and lastPolled
        res.lock.Lock()
        r.polling = false
        r.lastPolled = time.Nanoseconds()
        res.lock.Unlock()
    }
}

如果使用golang实现,拉取程序代码大致如下:

type Resource string

func Poller(in, out chan *Resource) {
    for r := range in {
        // poll the URL

        // send the processed Resource to out
        out <- r
    }
}

结合这两段代码,我对文中那句“名言”的理解:

传统多线程并发编程模型下,Poller接收的参数是需要被多个线程共享的资源(调用类似:pthread_create(thread_id, attr, Poller, resources)),而多个线程之间的同步依赖资源上的锁,这就是通过共享资源进行同步(communicate by sharing memory);而golang并发编程模型下,Poller接收的参数是两个channel(调用类似:go Poller(in, out)),只管从in channel中获取需要拉取的资源的URL,拉取完后,放入out channel,这两个channel就是整个并发程序中的通信载体,goroutine通过channel共享待拉取的资源的URL,这就是通过同步进行资源共享(share memory by communicating)。

其实在传统多线程并发编程模型下也可以实现golang的这个编程模型,例如封装一个channel出来:

type Resource string

struct In {
    data []Resource
    lock *sync.Mutex
    close bool
}

func (*in)Pop() Resource {
	res = ""
Loop:
	in.lock.Lock()
	if len(in.data) > 0 {
		res = in.data[0]
		in.data = in.data[1:]
	} else if(!in.close) {
		in.lock.Unlock()
		sleep
		goto Loop
	}
	in.lock.Unlock()
	return res
}

func (*in)Push(res Resource) {
	in.lock.Lock()
	in.data = append(in.data, res)
	in.lock.Unlock()
}

func (*in)Close() {
	in.close = true
}

// Out 类似

func Poller(in In, out Out) {
    for r := in.Pop(); r != ""; r = in.Pop() {
        // poll the URL

        // send the processed Resource to out
        out.Push(r)
    }
}

// 调用类似:pthread_create(thread_id, attr, Poller, in, out)

golang其实是在语言层面上通过goroutine和channel隐藏了这些东西。