什么时候应该在通道上使用互斥体?

mui*_*iiu 25 concurrency go

在过去的几周里,我一直在努力解决一个(不太)简单的问题:

什么时候最好使用sync.Mutex,反之,什么时候最好使用 a chan

似乎对于很多问题,任何一种策略都可以与另一种策略互换 -这就是问题所在!

观看Golang 文档中的这段视频。
下面,我冒昧地在操场上指定了代码,并将其翻译为sync.Mutex等效代码。

是否存在现实世界中遇到的特定问题值得使用另一个问题?

笔记:

  • 我非常喜欢这种用法chan,并且努力想出一种更优雅的实现方式sync.Mutex
  • 值得注意的是,该chan实现在同一时间内做了更多的工作(达到 12)*

游乐场:

乒乓球/乒乓球chan

package main

import (
    "fmt"
    "time"
)

type Ball struct { hits int }

func main() {
    table := make(chan *Ball)
    go player("ping", table)
    go player("pong", table)
    
    table <- new(Ball)
    time.Sleep(1 * time.Second)
    <-table
}

func player(name string, table chan *Ball) {
    for {
        ball := <-table
        ball.hits++
        fmt.Println(name, ball.hits)
        time.Sleep(100 * time.Millisecond)
        table <- ball
    }
}
Run Code Online (Sandbox Code Playgroud)

乒乓球/乒乓球sync.Mutex

package main

import (
    "fmt"
    "time"
    "sync"
)

type Ball struct { hits int }

var m =  sync.Mutex{}

func main() {
    ball := new(Ball)
    go player("ping", ball)
    go player("pong", ball)
    
    time.Sleep(1 * time.Second)
}

func player(name string, ball *Ball) {
    for {
        m.Lock()
        ball.hits++
        fmt.Println(name, ball.hits)
        time.Sleep(100 * time.Millisecond)
        m.Unlock()

    }
}
Run Code Online (Sandbox Code Playgroud)

Jer*_* An 15

在 Go 中,通道非常棒,您可以使用它们在 goroutine 之间进行通信。但是,为了方便起见,您可能希望在某些情况下使用sync.Mutex。这些情况如下:

  • 保护内部状态
  • 缓存问题
  • 为了更好的表现

这是三个例子和解释

  1. 一个简单的计数器

在此输入图像描述

  1. 乒乓球比赛

在此输入图像描述

  1. 最简单的缓存

在此输入图像描述


was*_*mup 3

一些渠道用例示例:

  • 使用缓冲通道容量(和/或长度)限制并发任务数量(例如运行的 goroutine 数量)
  • 转移对象的所有权(只有一个 goroutine 写入该对象,而没有其他 goroutine 读取它)
  • 协调、同步、发送信号和数据。

一些示例原语用例:

  • 保护结构的内部状态(例如sync.Mutex,或sync.RWMutex
  • 性能关键(取决于算法和用例,不是一般规则)

例子

为了清楚起见,假设我们需要一个一秒计数器,因此在下面的示例中,我们计数一秒,然后打印计数器值以查看其计数速度:

No |         Count |       Method
------------------------------------------------------
 1 |     17_729_027 | Using sync.RWMutex for increment   
 2 |     12_180_741 | Using channel for increment    
 3 |    106_743_095 | Using channel for timer 
 4 |    104_178_671 | Using time.AfterFunc and channel sync
Run Code Online (Sandbox Code Playgroud)

注:go版本go1.13.5 linux/amd64


代码:

1 - 用于sync.RWMutex增量:

package main

import (
    "sync"
    "time"
)

func main() {
    var i rwm
    go func() {
        for {
            i.inc() // free running counter
        }
    }()
    time.Sleep(1 * time.Second)
    println(i.read()) // sampling the counter
}

type rwm struct {
    sync.RWMutex
    i int
}

func (l *rwm) inc() {
    l.Lock()
    defer l.Unlock()
    l.i++
}
func (l *rwm) read() int {
    l.RLock()
    defer l.RUnlock()
    return l.i
}
Run Code Online (Sandbox Code Playgroud)

2 - 使用通道进行增量:

package main

import (
    "time"
)

func main() {
    ch := make(chan int, 1)
    ch <- 1
    timeout := time.NewTimer(1 * time.Second)
loop:
    for {
        select {
        case <-timeout.C:
            timeout.Stop()
            break loop
        default:
            ch <- 1 + <-ch
        }
    }

    println(<-ch)
}
Run Code Online (Sandbox Code Playgroud)

3 - 使用定时器通道:

package main

import "time"

func main() {
    ch := make(chan int)
    go func() {
        timeout := time.NewTimer(1 * time.Second)
        defer timeout.Stop()
        i := 1
        for {
            select {
            case <-timeout.C:
                ch <- i
                return
            default:
                i++
            }
        }
    }()

    println(<-ch)
}
Run Code Online (Sandbox Code Playgroud)

4 - 使用time.AfterFunc和通道同步:

package main

import (
    "fmt"
    "time"
)

func main() {
    d := 1 * time.Second
    i := uint64(0)
    ch := make(chan struct{})

    time.AfterFunc(d, func() {
        close(ch)
    })

loop:
    for {
        select {
        case <-ch:
            break loop
        default:
            i++
        }
    }

    fmt.Println(i) // 104_178_671
}

Run Code Online (Sandbox Code Playgroud)