如何在 golang 中使函数线程安全

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

如何在golang中锁定一个函数或函数体不被两个线程调用?

我的用例是我有一个调用串行接口的网络服务器,该接口一次只能有一个调用者,两个调用将通过在串行线路上彼此产生噪音来相互抵消。

go thread-safety mutex
4个回答
18
投票

最简单的方法是使用

sync.Mutex

package main

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

var lock sync.Mutex

func main() {
    go importantFunction("foo")
    go importantFunction("bar")
    time.Sleep(3 * time.Second)
}


func importantFunction(name string) {
    lock.Lock()
    defer lock.Unlock()
    fmt.Println(name)
    time.Sleep(1 * time.Second)
}

在这里你会看到“foo”和“bar”的打印间隔一秒,即使它们是 go routines。

去游乐场:https://play.golang.org/p/mXKl42zRW8


4
投票

Pylinux 使用 Mutex 的解决方案,正如他所说,可能是您的情况中最简单的。不过,我会在这里添加另一个作为替代方案。它可能适用于您的情况,也可能不适用。

不使用 Mutex,您可以让一个 goroutine 执行串行接口上的所有操作,并使用一个通道来序列化它需要执行的工作。 例子

package main

import (
    "fmt"
    "sync"
)

// handleCommands will handle commands in a serialized fashion
func handleCommands(opChan <-chan string) {
    for op := range opChan {
        fmt.Printf("command: %s\n", op)
    }
}

// produceCommands will generate multiple commands concurrently
func produceCommands(opChan chan<- string) {
    var wg sync.WaitGroup
    wg.Add(2)
    go func() { opChan <- "cmd1"; wg.Done() }()
    go func() { opChan <- "cmd2"; wg.Done() }()
    wg.Wait()
    close(opChan)
}

func main() {
    var opChan = make(chan string)
    go produceCommands(opChan)
    handleCommands(opChan)
}

相对于 Mutex 的优点是您可以更好地控制等待队列。使用 Mutex,队列隐式存在于

Lock()
,并且是无界的。另一方面,使用通道,您可以限制等待呼叫者的最大数量,并在同步呼叫站点过载时做出适当的反应。您还可以使用
len(opChan)
检查队列中有多少 goroutines。

编辑添加:

上述示例的一个限制(如评论中所述)是它不处理将计算结果返回给原始发送者。一种方法是在保持使用通道的方法的同时,为每个命令引入一个结果通道。因此,可以发送以下格式的结构,而不是通过命令通道发送字符串:

type operation struct {
    command string
    result  chan string
}

命令将按如下方式排队到命令通道:

func enqueueCommand(opChan chan<- operation, cmd string) <-chan string {
    var result = make(chan string)
    opChan <- operation{command: cmd, result: result}
    return result
}

这允许命令处理程序将值发送回命令的发起者。操场上的完整示例here.


4
投票

不可重入函数的实现方式有两种:

  • 阻塞:第一个调用者运行函数,后续调用者阻塞并等待函数退出,然后运行函数
  • 屈服:第一个调用者运行函数,后续调用者在函数执行时中止

这两种方法有不同的优点:

  • 阻塞的不可重入函数保证执行与尝试的次数一样多。但是,如果执行时间长,它们可能会积压,然后是爆发性的执行。
  • yield不可重入函数保证不拥塞不突发,可以保证最大执行率

阻塞不可重入函数最容易通过

mutex
实现,如@Pylinux 的回答中所述。 产生不可重入函数可以通过原子比较和交换来实现,如下所示:

import (
    "sync/atomic"
    "time"
)

func main() {
    tick := time.Tick(time.Second)
    var reentranceFlag int64
    go func() {
        for range tick {
            go CheckSomeStatus()
            go func() {
                if atomic.CompareAndSwapInt64(&reentranceFlag, 0, 1) {
                    defer atomic.StoreInt64(&reentranceFlag, 0)
                } else {
                    return
                }
                CheckAnotherStatus()
            }()
        }
    }()
}

在上面,

CheckAnotherStatus()
被保护以防止重新进入,这样第一个调用者将
reentranceFlag
设置为
1
,随后的调用者不这样做,并退出。

请考虑我的博文在 Golang 中实现不可重入函数 以进行更详细的讨论。


0
投票
type Semafor struct {
    sync.RWMutex
    semafor int
}

var mySemafor *Semafor

func (m *Semafor) get() int { //read lock
    if m != nil {
        m.RLock()
        defer m.RUnlock()
        return m.semafor
    } else {
        panic ("Error : The semaphore is not initialized, IntSemafor()")
    }
}

func (m *Semafor) set(val int) bool { //write lock
    ok := false
    if m != nil {
        if val != 0 {
            m.Lock()
            if m.semafor == 0 {
                m.semafor = val
                ok = true
            }
            m.Unlock()
        } else {
            m.Lock()
            m.semafor = val
            ok = true
            m.Unlock()
        }
    }
    return ok
}

func InitSemafor() {
    if mySemafor == nil {
        mySemafor = &Semafor{}
        mySemafor.set(0)
    }
}

func OnSemafor() bool {
    if mySemafor != nil {
        for !mySemafor.set(1) {
            for mySemafor.get() == 1 {
                SleepM(2)
            }
        }
      return true   
    } else {
      panic("Error : The semaphore is not initialized, InitSemafor()")
    }
}

func OffSemafor() {
    mySemafor.set(0)
}
© www.soinside.com 2019 - 2024. All rights reserved.