美文网首页消零燕归来
Go 共享内存通信

Go 共享内存通信

作者: hezhangjian | 来源:发表于2021-06-30 12:51 被阅读0次

翻译自

https://blog.golang.org/codelab-share

正文

传统线程模型(如Java、C++、Python)需要程序通过内存在线程间通信。典型的,共享数据结构被锁保护,线程通过争夺锁来访问这些数据。在某些情况下,通过线程安全的数据结构(如Python的队列,Java的ConcurrentHashMap)可以很容易地做到这一点。

Go的并发原语:goroutines和channels即协程和通道,提供了一种优雅、独特的方式来构建并发程序。相比明确地使用锁来共享数据,Go鼓励通过channel来传递数据或数据的引用。通过这样的方式,来保证同一时间只有一个协程获得数据。在Effective Go中是这样说的

Do not communicate by sharing memory; instead, share memory by communicating.
不要通过共享内存来通信,而是通过通信来共享内存

构想一个用来拉取URL列表的程序。传统的线程模型环境,可能会这么组织它的数据

type Resource struct {
    url string
    polling bool
    lastPolled int64
}

type Resources struct {
    data []*Resource
    lock *sync.Mutex
}

poller方法(多线程运行)可能会这么写

func Poller(res *Resources) {
    for {
        // get the least recently-polled Resource
        // and mark it as being polled
        res.lock.Lock()
        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()
    }
}

此功能大约有一页长,需要更多细节才能完成。 它甚至不包括 URL 轮询逻辑(可能只有几行)。并且,当resources池耗尽时的处理也不优雅。

让我们来看看使用go的原语该如何做,Poller是从input channel中获取Resources,然后将它们输出到output channel。

type Resource string

func Poller(int, out chan *Resource) {
    for r := range in {
        // poll the URL
        // send the proceed Resource to out
        out <- r
    }
}

前面例子上的脆弱逻辑在这里已经不存在了,并且Resources数据结构也不再包含用来同步的数据。事实上,剩下的才是重要的部分。专注于业务逻辑的实现,这正是简单语言特性的强大功能。

完整示例如下

// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
    "log"
    "net/http"
    "time"
)

const (
    numPollers     = 2                // number of Poller goroutines to launch
    pollInterval   = 60 * time.Second // how often to poll each URL
    statusInterval = 10 * time.Second // how often to log status to stdout
    errTimeout     = 10 * time.Second // back-off timeout on error
)

var urls = []string{
    "http://www.google.com/",
    "http://golang.org/",
    "http://blog.golang.org/",
}

// State represents the last-known state of a URL.
type State struct {
    url    string
    status string
}

// StateMonitor maintains a map that stores the state of the URLs being
// polled, and prints the current state every updateInterval nanoseconds.
// It returns a chan State to which resource state should be sent.
func StateMonitor(updateInterval time.Duration) chan<- State {
    updates := make(chan State)
    urlStatus := make(map[string]string)
    ticker := time.NewTicker(updateInterval)
    go func() {
        for {
            select {
            case <-ticker.C:
                logState(urlStatus)
            case s := <-updates:
                urlStatus[s.url] = s.status
            }
        }
    }()
    return updates
}

// logState prints a state map.
func logState(s map[string]string) {
    log.Println("Current state:")
    for k, v := range s {
        log.Printf(" %s %s", k, v)
    }
}

// Resource represents an HTTP URL to be polled by this program.
type Resource struct {
    url      string
    errCount int
}

// Poll executes an HTTP HEAD request for url
// and returns the HTTP status string or an error string.
func (r *Resource) Poll() string {
    resp, err := http.Head(r.url)
    if err != nil {
        log.Println("Error", r.url, err)
        r.errCount++
        return err.Error()
    }
    r.errCount = 0
    return resp.Status
}

// Sleep sleeps for an appropriate interval (dependent on error state)
// before sending the Resource to done.
func (r *Resource) Sleep(done chan<- *Resource) {
    time.Sleep(pollInterval + errTimeout*time.Duration(r.errCount))
    done <- r
}

func Poller(in <-chan *Resource, out chan<- *Resource, status chan<- State) {
    for r := range in {
        s := r.Poll()
        status <- State{r.url, s}
        out <- r
    }
}

func main() {
    // Create our input and output channels.
    pending, complete := make(chan *Resource), make(chan *Resource)

    // Launch the StateMonitor.
    status := StateMonitor(statusInterval)

    // Launch some Poller goroutines.
    for i := 0; i < numPollers; i++ {
        go Poller(pending, complete, status)
    }

    // Send some Resources to the pending queue.
    go func() {
        for _, url := range urls {
            pending <- &Resource{url: url}
        }
    }()

    for r := range complete {
        go r.Sleep(pending)
    }
}

相关文章

  • go并发通信

    go并发编程时,请记住:“不要通过共享内存来通信,而应该通过通信来共享内存” channel是Go语言在语言级别提...

  • golang并发总结

    golang并发模型 go在语言层面提供了内置的并发支持 不要通过共享内存来通信,而应该通过通信来共享内存 并发与...

  • Go 共享内存通信

    翻译自 https://blog.golang.org/codelab-share[https://blog.go...

  • go并发编程之美(一)

    一、前言 在Java中多线程之间是通过共享内存进行通信的,在go中多线程之间通信是基于消息的,go中的通道是go中...

  • Go 并发编程-共享变量

    在之前的文章中,我们详细说了 Go 语言中 goroutine + channel 通过通信的方式来共享内存,从而...

  • Go的Goroutine的使用

    并发 concurrency Goroutine 通过通信来共享内存,而不是通过共享内存来通信 Channel是G...

  • jvm内存模型

    线程通信:线程之间通过共享内存,消息传递的方式进行通信:共享内存通过读取内存中的公共状态来达到彼此间的通信 - 其...

  • Go 语言基础—— 通道(channel)

    通过通信来共享内存(Java是通过共享内存来通信的) 定义 通道创建语法: make(chan 元素类型, 容量)...

  • 共享内存

    Linux进程间通信 - 共享内存

  • Go channel-1

    不要以共享内存的方式去通信,而是以通信的方式共享内存 sync 鼓励使用channel channel的声明 ch...

网友评论

    本文标题:Go 共享内存通信

    本文链接:https://www.haomeiwen.com/subject/jfkrultx.html