英文:
Good way to return on locked mutex in go
问题
以下是翻译的内容:
以下问题:
我有一个函数,只允许一个调用者执行。
如果有人尝试调用该函数,而且它已经在忙碌中,第二个调用者应立即返回错误。
我尝试了以下方法:
1. 使用互斥锁
这个方法很简单。但问题是,你无法检查互斥锁是否被锁定。你只能在它上面阻塞。因此这种方法行不通。
2. 在通道上等待
var canExec = make(chan bool, 1)
func init() {
    canExec <- true
}
func onlyOne() error {
    select {
    case <-canExec:
    default:
        return errors.New("already busy")
    }
    defer func() {
        fmt.Println("done")
        canExec <- true
    }()
    // 做一些事情
}
我不喜欢这个方法的原因:
- 看起来很混乱
 - 很容易错误地在通道上阻塞/错误地向通道写入数据
 
3. 互斥锁和共享状态的混合
var open = true
var myMutex *sync.Mutex
func canExec() bool {
    myMutex.Lock()
    defer myMutex.Unlock()
    if open {
        open = false
        return true
    }
    return false
}
func endExec() {
    myMutex.Lock()
    defer myMutex.Unlock()
    open = true
}
func onlyOne() error {
    if !canExec() {
        return errors.New("busy")
    }
    defer endExec()
    // 做一些事情
    return nil
}
我也不喜欢这个方法。使用带有互斥锁的共享变量不太好。
还有其他的想法吗?
英文:
Following problem:
I have a function that only should allow one caller to execute.
If someone tries to call the function and it is already busy the second caller should immediatly return with an error.
I tried the following:
1. Use a mutex
Would be pretty easy. But the problem is, you cannot check if a mutex is locked. You can only block on it. Therefore it does not work
2. Wait on a channel
var canExec = make(chan bool, 1)
func init() {
	canExec <- true
}
func onlyOne() error {
	select {
	case <-canExec:
	default:
		return errors.New("already busy")
	}
	defer func() {
		fmt.Println("done")
		canExec <- true
	}()
	// do stuff
}
What I don't like here:
- looks really messi
 - if easy to mistakenly block on the channel / mistakenly write to the channel
 
3. Mixture of mutex and shared state
var open = true
var myMutex *sync.Mutex
func canExec() bool {
	myMutex.Lock()
	defer myMutex.Unlock()
	if open {
		open = false
		return true
	}
	return false
}
func endExec() {
	myMutex.Lock()
	defer myMutex.Unlock()
	open = true
}
func onlyOne() error {
	if !canExec() {
		return errors.New("busy")
	}
	defer endExec()
	// do stuff
	return nil
}
I don't like this either. Using a shard variable with mutex is not that nice.
Any other idea?
答案1
得分: 36
我会把你提供的内容翻译成中文,以下是翻译的结果:
我会提出我的偏好 - 使用atomic包。
var (
    locker    uint32
    errLocked = errors.New("被锁住了伙计")
)
func OneAtATime(d time.Duration) error {
    if !atomic.CompareAndSwapUint32(&locker, 0, 1) { // <-----------------------------
        return errLocked                             //   所有逻辑都在这四行里面         |
    }                                                //   这四行代码                   |
    defer atomic.StoreUint32(&locker, 0)             // <-----------------------------
    // 在这里进行逻辑处理,但是我们会休眠
    time.Sleep(d)
    return nil
}
这个想法非常简单。将初始值设置为0(uint32的零值)。函数中的第一件事是检查locker的值是否为0,如果是,则将其更改为1。所有这些操作都是原子性的。如果失败,简单地返回一个错误(或者以其他方式处理被锁定的状态)。如果成功,立即延迟将值(现在为1)替换为0。当然你不一定非要使用defer,但是如果在返回之前不将值设置回0,那么函数将无法再次运行。
在完成这4行设置之后,你可以按照正常的方式进行其他操作。
你可以通过为状态使用命名值来使代码更加清晰。
const (
    stateUnlocked uint32 = iota
    stateLocked
)
var (
    locker    = stateUnlocked
    errLocked = errors.New("被锁住了伙计")
)
func OneAtATime(d time.Duration) error {
    if !atomic.CompareAndSwapUint32(&locker, stateUnlocked, stateLocked) {
        return errLocked
    }
    defer atomic.StoreUint32(&locker, stateUnlocked)
    // 在这里进行逻辑处理,但是我们会休眠
    time.Sleep(d)
    return nil
}
英文:
I'll throw my preference out there - use the atomic package.
var (
	locker    uint32
	errLocked = errors.New("Locked out buddy")
)
func OneAtATime(d time.Duration) error {
	if !atomic.CompareAndSwapUint32(&locker, 0, 1) { // <-----------------------------
		return errLocked                             //   All logic in these         |
	}                                                //   four lines                 |
	defer atomic.StoreUint32(&locker, 0)             // <-----------------------------
	
    // logic here, but we will sleep
	time.Sleep(d)
	return nil
}
The idea is pretty simple.  Set the initial value to 0 (0 value of uint32).  The first thing you do in the function is check if the value of locker is currently 0 and if so it changes it to 1.  It does all of this atomically.  If it fails simply return an error (or however else you like to handle a locked state).  If successful, you immediately defer replacing the value (now 1) with 0.  You don't have to use defer obviously, but failing to set the value back to 0 before returning would leave you in a state where the function could no longer be run.
After you do those 4 lines of setup, you do whatever you would normally.
https://play.golang.org/p/riryVJM4Qf
You can make things a little nicer if desired by using named values for your states.
const (
	stateUnlocked uint32 = iota
	stateLocked
)
var (
	locker    = stateUnlocked
	errLocked = errors.New("Locked out buddy")
)
func OneAtATime(d time.Duration) error {
	if !atomic.CompareAndSwapUint32(&locker, stateUnlocked, stateLocked) {
		return errLocked
	}
	defer atomic.StoreUint32(&locker, stateUnlocked)
	
    // logic here, but we will sleep
	time.Sleep(d)
	return nil
}
答案2
得分: 31
你可以使用信号量来实现这个(go get golang.org/x/sync/semaphore)
package main
import (
	"errors"
	"fmt"
	"sync"
	"time"
	"golang.org/x/sync/semaphore"
)
var sem = semaphore.NewWeighted(1)
func main() {
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if err := onlyOne(); err != nil {
				fmt.Println(err)
			}
		}()
		time.Sleep(time.Second)
	}
	wg.Wait()
}
func onlyOne() error {
	if !sem.TryAcquire(1) {
		return errors.New("busy")
	}
	defer sem.Release(1)
	fmt.Println("working")
	time.Sleep(5 * time.Second)
	return nil
}
希望对你有帮助!
英文:
You can use a semaphore for this (go get golang.org/x/sync/semaphore)
package main
import (
	"errors"
	"fmt"
	"sync"
	"time"
	"golang.org/x/sync/semaphore"
)
var sem = semaphore.NewWeighted(1)
func main() {
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if err := onlyOne(); err != nil {
				fmt.Println(err)
			}
		}()
		time.Sleep(time.Second)
	}
	wg.Wait()
}
func onlyOne() error {
	if !sem.TryAcquire(1) {
		return errors.New("busy")
	}
	defer sem.Release(1)
	fmt.Println("working")
	time.Sleep(5 * time.Second)
	return nil
}
答案3
得分: 4
你可以使用带有select语句的标准通道方法。
var (
    ch = make(chan bool)
)
func main() {
    i := 0
    wg := sync.WaitGroup{}
    for i < 100 {
        i++
        wg.Add(1)
        go func() {
            defer wg.Done()
            err := onlyOne()
            if err != nil {
                fmt.Println("错误:", err)
            } else {
                fmt.Println("好的")
            }
        }()
        go func() {
            ch <- true
        }()
    }
    wg.Wait()
}
func onlyOne() error {
    select {
    case <-ch:
        // 做一些事情
        return nil
    default:
        return errors.New("忙碌")
    }
}
请注意,我只翻译了代码部分,其他内容被忽略了。
英文:
You could use standard channel approach with select statement.
var (
	ch = make(chan bool)
)
func main() {
	i := 0
	wg := sync.WaitGroup{}
	for i < 100 {
		i++
		wg.Add(1)
		go func() {
			defer wg.Done()
			err := onlyOne()
			if err != nil {
				fmt.Println("Error: ", err)
			} else {
				fmt.Println("Ok")
			}
		}()
		go func() {
			ch <- true
		}()
	}
	wg.Wait()
}
func onlyOne() error {
	select {
	case <-ch:
		// do stuff
		return nil
	default:
		return errors.New("Busy")
	}
}
答案4
得分: 4
你想要一个函数只执行一次,还是在给定的时间内执行一次?如果是前者,请参考https://golang.org/pkg/sync/#Once。
如果你想要一次执行一个解决方案:
package main
import (
	"fmt"
	"sync"
	"time"
)
// OnceAtATime 保护函数不被同时执行。
// 示例:
//    func myFunc() { time.Sleep(10*time.Second) }
//    func main() {
//        once := OnceAtATime{}
//        once.Do(myFunc)
//        once.Do(myFunc) // 不会执行
//    }
type OnceAtATime struct {
	m        sync.Mutex
	executed bool
}
func (o *OnceAtATime) Do(f func()) {
	o.m.Lock()
	if o.executed {
		o.m.Unlock()
		return
	}
	o.executed = true
	o.m.Unlock()
	f()
	o.m.Lock()
	o.executed = false
	o.m.Unlock()
}
// Proof of concept
func f(m int, done chan<- struct{}) {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d: %d\n", m, i)
		time.Sleep(250 * time.Millisecond)
	}
	close(done)
}
func main() {
	done := make(chan struct{})
	once := OnceAtATime{}
	go once.Do(func() { f(1, done) })
	go once.Do(func() { f(2, done) })
	<-done
	done = make(chan struct{})
	go once.Do(func() { f(3, done) })
	<-done
}
链接:https://play.golang.org/p/nZcEcWAgKp
英文:
Do you want a function to be executed exactly once or once at given time? In former case take a look at https://golang.org/pkg/sync/#Once.
If you want once at a time solution:
package main
import (
	"fmt"
	"sync"
	"time"
)
// OnceAtATime protects function from being executed simultaneously.
// Example:
//    func myFunc() { time.Sleep(10*time.Second) }
//    func main() {
//        once := OnceAtATime{}
//        once.Do(myFunc)
//        once.Do(myFunc) // not executed
//    }
type OnceAtATime struct {
	m        sync.Mutex
	executed bool
}
func (o *OnceAtATime) Do(f func()) {
	o.m.Lock()
	if o.executed {
		o.m.Unlock()
		return
	}
	o.executed = true
	o.m.Unlock()
	f()
	o.m.Lock()
	o.executed = false
	o.m.Unlock()
}
// Proof of concept
func f(m int, done chan<- struct{}) {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d: %d\n", m, i)
		time.Sleep(250 * time.Millisecond)
	}
	close(done)
}
func main() {
	done := make(chan struct{})
	once := OnceAtATime{}
	go once.Do(func() { f(1, done) })
	go once.Do(func() { f(2, done) })
	<-done
	done = make(chan struct{})
	go once.Do(func() { f(3, done) })
	<-done
}
答案5
得分: 2
但问题是,你无法检查互斥锁是否被锁定。你只能在其上阻塞。因此,它不起作用。
可能在 Go 1.18(2022年第一季度)中,你将能够测试互斥锁是否被锁定...而不会阻塞。
参考 Go 101 提到的 issue 45435,由 Tye McQueen 提出:
sync: 添加 Mutex.TryLock
随后是 CL 319769,但有一个警告:
这些函数的使用几乎总是一个坏主意(但不总是)。
很少情况下它们是必要的,并且第三方实现(例如使用互斥锁和原子操作)无法像 sync 包本身的实现那样与竞争检测器集成。
最初的反对意见(已经撤回)是:
锁用于保护不变量。
如果锁被其他人持有,你对不变量无法做出任何断言。
TryLock 鼓励对锁的不精确思考;它鼓励对可能为真或可能为假的不变量做出假设。这最终成为竞争的源头。
再思考一下,将 TryLock 构建到 Mutex 中与使用包装器相比有一个重要的好处:
失败的 TryLock 调用不会创建虚假的 happens-before 边缘来困惑竞争检测器。
还有:
基于通道的实现是可能的,但性能比较差。
我们之所以有 sync.Mutex 而不仅仅使用通道进行锁定,是有原因的。
英文:
> But the problem is, you cannot check if a mutex is locked. You can only block on it. Therefore it does not work
With possible Go 1.18 (Q1 2022), you will be able to test if a mutex is locked... without blocking on it.
See (as mentioned by Go 101) the issue 45435 from Tye McQueen :
> ## sync: add Mutex.TryLock
This is followed by CL 319769, with the caveat:
> Use of these functions is almost (but not) always a bad idea.
>
> Very rarely they are necessary, and third-party implementations (using a mutex and an atomic word, say) cannot integrate as well with the race detector as implementations in package sync itself.
The objections (since retracted) were:
> Locks are for protecting invariants.
If the lock is held by someone else, there is nothing you can say about the invariant.
>
> TryLock encourages imprecise thinking about locks; it encourages making assumptions about the invariants that may or may not be true.
That ends up being its own source of races.
>
> Thinking more about this, there is one important benefit to building TryLock into Mutex, compared to a wrapper:
failed TryLock calls wouldn't create spurious happens-before edges to confuse the race detector.
And:
> A channel-based implementation is possible, but performs poorly in comparison.
There's a reason we have sync.Mutex rather than just using channel for locking.
答案6
得分: 0
我为此提出了以下通用解决方案:
对我来说可以工作,你看到有什么问题吗?
import (
	"sync"
)
const (
	ONLYONECALLER_LOCK = "onlyonecaller"
	ANOTHER_LOCK       = "onlyonecaller"
)
var locks = map[string]bool{}
var mutex = &sync.Mutex{}
func Lock(lock string) bool {
	mutex.Lock()
	defer mutex.Unlock()
	locked, ok := locks[lock]
	if !ok {
		locks[lock] = true
		return true
	}
	if locked {
		return false
	}
	locks[lock] = true
	return true
}
func IsLocked(lock string) bool {
	mutex.Lock()
	defer mutex.Unlock()
	locked, ok := locks[lock]
	if !ok {
		return false
	}
	return locked
}
func Unlock(lock string) {
	mutex.Lock()
	defer mutex.Unlock()
	locked, ok := locks[lock]
	if !ok {
		return
	}
	if !locked {
		return
	}
	locks[lock] = false
}
参见:https://play.golang.org/p/vUUsHcT3L-
英文:
I came up with the following generic solution for that:
Works for me, or do you see any problem with that?
import (
 "sync"
)
const (
	ONLYONECALLER_LOCK = "onlyonecaller"
	ANOTHER_LOCK = "onlyonecaller"
)
var locks = map[string]bool{}
var mutex = &sync.Mutex{}
func Lock(lock string) bool {
	mutex.Lock()
	defer mutex.Unlock()
	locked, ok := locks[lock]
	if !ok {
		locks[lock] = true
		return true
	}
	if locked {
		return false
	}
	locks[lock] = true
	return true
}
func IsLocked(lock string) bool {
	mutex.Lock()
	defer mutex.Unlock()
	locked, ok := locks[lock]
	if !ok {
		return false
	}
	return locked
}
func Unlock(lock string) {
	mutex.Lock()
	defer mutex.Unlock()
	locked, ok := locks[lock]
	if !ok {
		return
	}
	if !locked {
		return
	}
	locks[lock] = false
}
答案7
得分: 0
这个包怎么样:https://github.com/viney-shih/go-lock。它使用通道和信号量(golang.org/x/sync/semaphore)来解决你的问题。
go-lock除了提供Lock和Unlock函数外,还实现了TryLock、TryLockWithTimeout和TryLockWithContext函数。它提供了灵活性来控制资源。
示例代码:
package main
import (
    "fmt"
    "time"
    "context"
    lock "github.com/viney-shih/go-lock"
)
func main() {
    casMut := lock.NewCASMutex()
    casMut.Lock()
    defer casMut.Unlock()
    // TryLock without blocking
    fmt.Println("Return", casMut.TryLock()) // 返回 false
    // TryLockWithTimeout without blocking
    fmt.Println("Return", casMut.TryLockWithTimeout(50*time.Millisecond)) // 返回 false
    // TryLockWithContext without blocking
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()
    fmt.Println("Return", casMut.TryLockWithContext(ctx)) // 返回 false
    // 输出:
    // Return false
    // Return false
    // Return false
}
英文:
How about this package: https://github.com/viney-shih/go-lock . It use channel and semaphore (golang.org/x/sync/semaphore) to solve your problem.
go-lock implements TryLock, TryLockWithTimeout and TryLockWithContext functions in addition to Lock and Unlock. It provides flexibility to control the resources.
Examples:
package main
import (
    "fmt"
    "time"
    "context"
    lock "github.com/viney-shih/go-lock"
)
func main() {
    casMut := lock.NewCASMutex()
    casMut.Lock()
    defer casMut.Unlock()
    // TryLock without blocking
    fmt.Println("Return", casMut.TryLock()) // Return false
    // TryLockWithTimeout without blocking
    fmt.Println("Return", casMut.TryLockWithTimeout(50*time.Millisecond)) // Return false
    // TryLockWithContext without blocking
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()
    fmt.Println("Return", casMut.TryLockWithContext(ctx)) // Return false
    
    // Output:
    // Return false
    // Return false
    // Return false
}
答案8
得分: 0
让我们保持简单:
    package main
    
    import (
     "fmt"
     "time"
     "golang.org/x/sync/semaphore"
    )
    var sem *semaphore.NewWeighted(1)
    func init() {
      sem = semaphore.NewWeighted(1)
    }
    func doSomething() {
      if !sem.TryAcquire(1) {
        return errors.New("我很忙")
      }
      defer sem.Release(1)
      fmt.Println("我正在做我的工作,然后我会小睡一会儿")
      time.Sleep(10)
    }
    func main() {
      go func() {
        doSomething()
      }()
    }
英文:
Lets keep it simple:
    package main
    
    import (
     "fmt"
     "time"
     "golang.org/x/sync/semaphore"
    )
    var sem *semaphore.NewWeighted(1)
    func init() {
      sem = emaphore.NewWeighted(1)
    }
    func doSomething() {
      if !sem.TryAcquire(1) {
        return errors.New("I'm busy")
      }
      defer sem.Release(1)
      fmt.Println("I'm doing my work right now, then I'll take a nap")
      time.Sleep(10)
    }
    func main() {
      go func() {
        doSomething()
      }()
    }
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论