如何锁定函数或函数体不被golang中的两个线程调用?
我的用例是我有一个调用串行接口的网络服务器,一次只能有一个呼叫者,两个呼叫将通过在串行线路上相互产生噪声来相互抵消。
答案 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)
}
在这里你会看到“第一个”和“第二个”被打印一秒钟,即使它们是常规的。
答案 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进行更详细的讨论。