如何在golang中锁定一个函数或函数体不被两个线程调用?
我的用例是我有一个调用串行接口的网络服务器,该接口一次只能有一个调用者,两个调用将通过在串行线路上彼此产生噪音来相互抵消。
最简单的方法是使用
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。
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.
不可重入函数的实现方式有两种:
这两种方法有不同的优点:
阻塞不可重入函数最容易通过
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 中实现不可重入函数 以进行更详细的讨论。
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)
}