一、 竞争条件

多个 goroutine 并发访问同一变量时,如果存在对该变量的写操作。就会发生竞争。 go 的口头禅:不要使用共享数据来通信;使用通信来共享数据。

  1. 避免竞争方法

方法一、不要去写共享变量 方法二、避免从多个goroutine访问变量 方法三、使用互斥锁

二、 sync.Mutex互斥锁

  1. 二元信号量

一个只能为0和1信号量叫做二元信号量

    var (
        sema = make(chan struct{}, 1) // a binary semaphore guarding balance
        balance int
    )
    func Deposit(amount int) {
        sema <- struct{}{} // acquire token
        balance = balance + amount
        <-sema // release token
    }
    func Balance() int {
        sema <- struct{}{} // acquire token
        b := balance
        <-sema // release token
        return b
    }
  1. sync 包中的 Mutex 类似二元信号量
    import "sync"

    var (
        mu      sync.Mutex
        balance int
    )

    func Deposit(amount int) {
        mu.Lock()
        //defer mu.Unlock()
        //临界区开始
        balance = balance + amount
        //临界区结束
        mu.Unlock() 
    }

    func Balance() int {
        mu.Lock()
        defer mu.Unlock()

        return balance
    }
  1. 没有重入锁

go 中没有重入锁,一旦锁被Lock,其它的goroutine将不能获取锁 当需要重入锁时,可利用封装方法,将导致重入锁的代码分离,单独封装后使用

    // NOTE: incorrect!
    var (
        mu      sync.Mutex
        balance int
    )

    func Withdraw(amount int) bool {
        mu.Lock()
        defer mu.Unlock()
        Deposit(-amount)
        if Balance() < 0 {
            Deposit(amount)
            return false // insufficient funds
        }
        return true
    }

    // right
    func Withdraw(amount int) bool {
        mu.Lock()
        defer mu.Unlock()
        deposit(-amount)
        if balance < 0 {
            deposit(amount)
            return false // insufficient funds
        }
        return true
    }

    func Deposit(amount int) {
        mu.Lock()
        defer mu.Unlock()
        deposit(amount)
    }

    func Balance() int {
        mu.Lock()
        defer mu.Unlock()
        return balance
    }

    // This function requires that the lock be held.
    func deposit(amount int) { balance += amount }

三、 sync.RWMutex读写锁

  1. 多读单写锁

多个只读操作并行执行,但写操作会完全互斥, 这种锁叫作“多读单写”锁((multiple readers, single writer lock)

    var (
        mu      sync.RWMutex
        balance int
    )

    func Withdraw(amount int) bool {
        mu.Lock()
        defer mu.Unlock()
        deposit(-amount)
        if balance < 0 {
            deposit(amount)
            return false // insufficient funds
        }
        return true
    }

    func Deposit(amount int) {
        mu.Lock()
        defer mu.Unlock()
        deposit(amount)
    }

    func Balance() int {
        mu.RLock()  //这里调用的读锁,其它保持不变
        defer mu.RUnlock()
        return balance
    }

    // This function requires that the lock be held.
    func deposit(amount int) { balance += amount }

    //sync.RWMutex 一般比 sync.Mutex 要慢一些

四、 内存同步

尽量将变量限定在goroutine内部;如果是多个goroutine都需要访问的变量,使用互斥条件来访问。

五、 sync.Once初始化

  1. 使用读写锁的时候,读并发,写互斥
    var mu sync.RWMutex // guards icons
    var icons map[string]image.Image

    // Concurrency-safe.
    func Icon(name string) image.Image {
        mu.RLock()
        if icons != nil {
            icon := icons[name]
            mu.RUnlock()
            return icon
        }
        mu.RUnlock()
        // acquire an exclusive lock
        mu.Lock()
        if icons == nil { // NOTE: must recheck for nil
            loadIcons()
        }

        icon := icons[name]
        mu.Unlock()
        return icon
    }
  1. 使用 sync.Once 解决一次性初始化问题
    var loadIconsOnce sync.Once
    var icons map[string]image.Image
    // Concurrency-safe.
    func Icon(name string) image.Image {
        loadIconsOnce.Do(loadIcons)
        return icons[name]
    }

五、 并发非阻塞缓存

  1. 锁和通道形式
    package memo

    import "sync"

    // Func is the type of the function to memoize.
    type Func func(string) (interface{}, error)

    type result struct {
        value interface{}
        err   error
    }

    //!+
    type entry struct {
        res   result
        ready chan struct{} // closed when res is ready
    }

    func New(f Func) *Memo {
        return &Memo{f: f, cache: make(map[string]*entry)}
    }

    type Memo struct {
        f     Func
        mu    sync.Mutex // guards cache
        cache map[string]*entry
    }

    func (memo *Memo) Get(key string) (value interface{}, err error) {
        memo.mu.Lock()
        e := memo.cache[key]
        if e == nil {
            // This is the first request for this key.
            // This goroutine becomes responsible for computing
            // the value and broadcasting the ready condition.
            e = &entry{ready: make(chan struct{})}
            memo.cache[key] = e
            memo.mu.Unlock()

            e.res.value, e.res.err = memo.f(key)

            close(e.ready) // broadcast ready condition
        } else {
            // This is a repeat request for this key.
            memo.mu.Unlock()

            <-e.ready // wait for ready condition
        }
        return e.res.value, e.res.err
    }
  1. monitor goroutine
    package memo

    //!+Func

    // Func is the type of the function to memoize.
    type Func func(key string) (interface{}, error)

    // A result is the result of calling a Func.
    type result struct {
        value interface{}
        err   error
    }

    type entry struct {
        res   result
        ready chan struct{} // closed when res is ready
    }

    //!-Func

    //!+get

    // A request is a message requesting that the Func be applied to key.
    type request struct {
        key      string
        response chan<- result // the client wants a single result
    }

    type Memo struct{ requests chan request }

    // New returns a memoization of f.  Clients must subsequently call Close.
    func New(f Func) *Memo {
        memo := &Memo{requests: make(chan request)}
        go memo.server(f)
        return memo
    }

    func (memo *Memo) Get(key string) (interface{}, error) {
        response := make(chan result)
        memo.requests <- request{key, response}
        res := <-response
        return res.value, res.err
    }

    func (memo *Memo) Close() { close(memo.requests) }

    //!-get

    //!+monitor

    func (memo *Memo) server(f Func) {
        cache := make(map[string]*entry)
        for req := range memo.requests {
            e := cache[req.key]
            if e == nil {
                // This is the first request for this key.
                e = &entry{ready: make(chan struct{})}
                cache[req.key] = e
                go e.call(f, req.key) // call f(key)
            }
            go e.deliver(req.response)
        }
    }

    func (e *entry) call(f Func, key string) {
        // Evaluate the function.
        e.res.value, e.res.err = f(key)
        // Broadcast the ready condition.
        close(e.ready)
    }

    func (e *entry) deliver(response chan<- result) {
        // Wait for the ready condition.
        <-e.ready
        // Send the result to the client.
        response <- e.res
    }

    //!-monitor
  1. 结论

上面的两种方案 (锁与通信) 并不好说特定情境下哪种更好,不过了解他们还是有价值的。 有时候从一种方式切换到另一种可以使你的代码更为简洁。

六、 Goroutines 和线程

  1. 每个 OS 线程都有一个固定大小的内存块(一般会是 2 MB)来做栈;Goroutine 只需要 2 KB做栈开始
  2. OS 线程栈是固定的;Goroutine 栈是动态伸缩的,最大可至 1 GB
  3. Go 调度器工作更快,更简单
  4. GOMAXPROCS 决定有多少个线程同时执行 GO 代码
  5. 线程有 ID 号,Groutine 没有 ID 号