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

问题描述 投票:0回答:2

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

什么时候最好使用
sync.Mutex
,相反,什么时候最好使用
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
    }
}

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()

    }
}
go concurrency
2个回答
15
投票

在 Go 中,通道非常棒,你可以使用它们在 goroutine 之间进行通信。但是,为了方便起见,在某些情况下您可能需要使用

sync.Mutex
。 这些情况如下:

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

这里有三个示例和解释

  1. 一个简单的计数器

  1. 乒乓球比赛

  1. 最简单的缓存


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

注意: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
}

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)
}

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)
}

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
}

© www.soinside.com 2019 - 2024. All rights reserved.