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

时间:2017-09-10 07:39:41

标签: go thread-safety mutex

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

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

3 个答案:

答案 0 :(得分:6)

最简单的方法是使用sync.Mutex

package main

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

var lock sync.Mutex

func main() {
    go importantFunction("first")
    go importantFunction("second")
    time.Sleep(3 * time.Second)
}


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

在这里你会看到“第一个”和“第二个”被打印一秒钟,即使它们是常规的。

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

答案 1 :(得分:3)

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

您可以使用单个goroutine执行串行接口上​​的所有操作,而不是使用Mutex,并使用通道序列化它需要执行的工作。 Example

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的优点是您可以更好地控制等待队列。使用互斥锁时,队列隐含地存在于Lock(),并且是无限制的。另一方面,如果同步的呼叫站点过载,您可以使用频道限制等待的最大呼叫者数量并做出适当的反应。您还可以使用len(opChan)检查队列中有多少个goroutine。

编辑以添加:

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

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上的完整示例。

答案 2 :(得分:0)

实现非重入函数有两种方法:

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

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

  • 保证阻止非重入函数的次数与尝试次数相同。但是,如果执行时间过长,则可能是积压,然后执行突发。
  • 产生不可重入的功能可确保不拥塞且无突发,并可保证最高执行率。

阻止非重入函数最容易通过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,后续调用者无法执行相同操作,并退出。

请考虑我的博文Implementing non re-entrant functions in Golang进行更详细的讨论。