And if, when that function returns, it becomes available for a lock? Why not let blocks happen as they naturally would and work on other things in other go routines? You can do something like you want with the select statement and channels if you need to choose what to do based on what's currently blocking.
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
That makes sense. What about a LockedIfPossible method, though? It behaves the same way as Lock if a lock can be obtained, otherwise it lets the user know that the lock is currently unobtainable, which, in the normal use cases, means "don't do that thing you were about to do."
This has been asked for a number of times in the past and rejected
because it leads people to believe in things that are not justifiable.
Example:
if m.Locked() {
// Is m truly locked here?
}
On Mon, Aug 12, 2013 at 7:29 AM, Dan Kortschak <dan.ko...@adelaide.edu.au> wrote:This has been asked for a number of times in the past and rejected
because it leads people to believe in things that are not justifiable.
Example:
if m.Locked() {
// Is m truly locked here?
}Then we should delete time.Now()now := time.Now()// Is it still the same time here?
Just to be clear, there are pretty significant differences between at least some of those and the lock case.
To make things worse, everything is in the past by the time you know about it. We manage, but when you intend to own something there is a fairly strong semantic imperative to know whether it is possible to own it.
--
41 func (m *Mutex) Lock() { 42 // Fast path: grab unlocked mutex. 43 if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { 44 if raceenabled { 45 raceAcquire(unsafe.Pointer(m)) 46 } 47 return 48 } 49 50 awoke := false 51 for { 52 old := m.state 53 new := old | mutexLocked 54 if old&mutexLocked != 0 { 55 new = old + 1<<mutexWaiterShift 56 } 57 if awoke { 58 // The goroutine has been woken from sleep, 59 // so we need to reset the flag in either case. 60 new &^= mutexWoken 61 } 62 if atomic.CompareAndSwapInt32(&m.state, old, new) { 63 if old&mutexLocked == 0 { 64 break 65 } 66 runtime_Semacquire(&m.sema) 67 awoke = true 68 } 69 } 70 71 if raceenabled { 72 raceAcquire(unsafe.Pointer(m)) 73 } 74 }
You could easily modify that to not loop, and you'd get LockIfPossible. Something like this (haven't tested this):
41 func (m *Mutex) LockIfPossible() bool { 42 // Fast path: grab unlocked mutex. 43 if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { 44 if raceenabled { 45 raceAcquire(unsafe.Pointer(m)) 46 } 47 return true 48 }
48 return false
49 }
This is great if you're only doing a "TryLock". "Lock" and "TryLock" in different functions on the same mutex are a different story.It should be as simple as this:func (m *Mutex) TryLock() (locked bool) {locked = atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)if locked && raceenabled {raceAcquire(unsafe.Pointer(m))
On Tue, Aug 13, 2013 at 12:13 AM, Luke Scott <lu...@visionlaunchers.com> wrote:This is great if you're only doing a "TryLock". "Lock" and "TryLock" in different functions on the same mutex are a different story.It should be as simple as this:func (m *Mutex) TryLock() (locked bool) {locked = atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)if locked && raceenabled {raceAcquire(unsafe.Pointer(m))
This race part is not necessary. Users of the language implementation and the std lib are not intended to use them. It should just work.
I've posted a feature request here: https://code.google.com/p/go/issues/detail?id=6123
Feel free to chime in. Looking for reasons to include:func (m *Mutex) TryLock() bool {} // Trys to Lock(), returns true if Lock was obtained, false otherwise. Doesn't block.At this point the name isn't important. TryLock/LockIfPossible/GetLock()/...Personally I would want a TryLock in any situation where I would need to Lock() and TryLock() on the same mutex in different functions. Lock() would be used in a function that is expected to block, and TryLock() would be in a function that is expected not to block.
I'm not currently hurting for TryLock(), but it is in every language that provides a mutex that I could find. I feel that TryLock() should be included for completeness.
I am hurting for LockBefore(Time) though. Specifically doing something like net.Pipe where I need to set a deadline. Currently net.Pipe does not support read/write deadlines.
Luke
On Sunday, August 11, 2013 8:23:35 PM UTC-7, Joshua Liebow-Feeser wrote:Recently, I found myself wanting to be able to inspect a sync.Mutex to find out if it was locked without actually blocking. Since this isn't possible without using unsafe, I came up with a workaround:
http://godoc.org/github.com/joshlf13/sync
However, that workaround works about 2x slower than the standard sync.Mutex. What do people think of adding that functionality (in particular, two methods: LockIfPossible, which does what it sounds like, and Locked, which checks the state of the mutex - neither blocks execution)?
Cheers,
Josh
--
On Mon, Aug 12, 2013 at 11:08 PM, Luke Scott <lu...@visionlaunchers.com> wrote:I've posted a feature request here: https://code.google.com/p/go/issues/detail?id=6123My vote is that this feature should remain unimplemented. I have written a lot of Go, and have never felt the lack of a TryLock-like concept. That's not for lack of having written code (mostly in Java) using it before I switched to Go, either :).Feel free to chime in. Looking for reasons to include:func (m *Mutex) TryLock() bool {} // Trys to Lock(), returns true if Lock was obtained, false otherwise. Doesn't block.At this point the name isn't important. TryLock/LockIfPossible/GetLock()/...Personally I would want a TryLock in any situation where I would need to Lock() and TryLock() on the same mutex in different functions. Lock() would be used in a function that is expected to block, and TryLock() would be in a function that is expected not to block.Code is much simpler if you construct it with blocking calls and use goroutines when you need concurrency. When you say "that is expected not to block" it sounds like you're looking for EAGAIN-like semantics, which seem to make the caller code harder to do correctly (i.e. without busy-waiting or race conditions).I'm not currently hurting for TryLock(), but it is in every language that provides a mutex that I could find. I feel that TryLock() should be included for completeness.That's not a good enough reason to support them :). Pretty much every language with new has free, with classes has exceptions, etc.I am hurting for LockBefore(Time) though. Specifically doing something like net.Pipe where I need to set a deadline. Currently net.Pipe does not support read/write deadlines.You can easily construct your own net.Conn or io.ReadWriter pipe implementation that uses channels internally, and thus has select, and thus can have deadlines and timeouts.
On Monday, August 12, 2013 2:21:12 PM UTC-7, Dmitry Vyukov wrote:On Tue, Aug 13, 2013 at 12:13 AM, Luke Scott <lu...@visionlaunchers.com> wrote:This is great if you're only doing a "TryLock". "Lock" and "TryLock" in different functions on the same mutex are a different story.It should be as simple as this:func (m *Mutex) TryLock() (locked bool) {locked = atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)if locked && raceenabled {raceAcquire(unsafe.Pointer(m))
This race part is not necessary. Users of the language implementation and the std lib are not intended to use them. It should just work.What I posted was just a slimmed down version of Lock(). Why would it not be necessary if Lock() does it for the "short path"?
"Complexity of using TryLock correctly: high"How so? A Lock should always be accompanied by an Unlock in the same function, typically using a defer. It would look something like this, typically:if !m.TryLock() {return errors.New("Busy")}defer m.Unlock()// Continue with function…How is that complex? Now if you're doing a Lock in one function and an Unlock in another, that's where it complex. But in a majority of the cases you shouldn't be doing that. In that case you have the same problem with Lock as TryLock - TryLock is not any different than Lock other than it doesn't block.
"Clarify of programs using channels instead: higher"Really? So this is clearer:select {case <-lock:defer func() {lock <-true}()// Continue with function…default:return erros.New("Busy")}No offense, but I prefer TryLock way more in simple situations like this. I rarely use a Lock without an Unlock in the same function. TryLock would be used exactly the same way.The TryLock is also faster because you have a single mutex. A channel is much more complex under the hood. That's why I want to have the option to go with something more primitive if I want to.
On Tue, Aug 13, 2013 at 2:32 PM, Luke Scott <lu...@visionlaunchers.com> wrote:"Complexity of using TryLock correctly: high"How so? A Lock should always be accompanied by an Unlock in the same function, typically using a defer. It would look something like this, typically:if !m.TryLock() {return errors.New("Busy")}defer m.Unlock()// Continue with function…How is that complex? Now if you're doing a Lock in one function and an Unlock in another, that's where it complex. But in a majority of the cases you shouldn't be doing that. In that case you have the same problem with Lock as TryLock - TryLock is not any different than Lock other than it doesn't block.You're not showing the whole picture. What does the caller do? The caller now needs to do something like:backoff := 100*time.Millisecondfor {if err := f(); err != nil {if err == errors.New("Busy") {time.Sleep(backoff)backoff *= 2if backoff > 1*time.Second {backoff = 1*time.Second}continue}}break}... and that's really complex.
"Clarify of programs using channels instead: higher"Really? So this is clearer:select {case <-lock:defer func() {lock <-true}()// Continue with function…default:return erros.New("Busy")}No offense, but I prefer TryLock way more in simple situations like this. I rarely use a Lock without an Unlock in the same function. TryLock would be used exactly the same way.The TryLock is also faster because you have a single mutex. A channel is much more complex under the hood. That's why I want to have the option to go with something more primitive if I want to.Of course that's not clearer. That's still a TryLock. The whole pattern is the problem, not the implementation. Tell me what you want to use a TryLock to accomplish, and I'll either tell you whether I think there's a better way.
On Aug 13, 2013, at 2:44 PM, Kyle Lemons <kev...@google.com> wrote:On Tue, Aug 13, 2013 at 2:32 PM, Luke Scott <lu...@visionlaunchers.com> wrote:
"Complexity of using TryLock correctly: high"How so? A Lock should always be accompanied by an Unlock in the same function, typically using a defer. It would look something like this, typically:if !m.TryLock() {return errors.New("Busy")}defer m.Unlock()// Continue with function…How is that complex? Now if you're doing a Lock in one function and an Unlock in another, that's where it complex. But in a majority of the cases you shouldn't be doing that. In that case you have the same problem with Lock as TryLock - TryLock is not any different than Lock other than it doesn't block.You're not showing the whole picture. What does the caller do? The caller now needs to do something like:backoff := 100*time.Millisecondfor {if err := f(); err != nil {if err == errors.New("Busy") {time.Sleep(backoff)backoff *= 2if backoff > 1*time.Second {backoff = 1*time.Second}continue}}break}... and that's really complex.That is a pure assumption, and has nothing to do with the locking implementation itself. The code I have shown deals with just the locking. If my code were anything like that I would be better off with just a regular Lock(). The whole point of TryLock is to abort if the lock cannot be established instead of block.
"Clarify of programs using channels instead: higher"Really? So this is clearer:select {case <-lock:defer func() {lock <-true}()// Continue with function…default:return erros.New("Busy")}No offense, but I prefer TryLock way more in simple situations like this. I rarely use a Lock without an Unlock in the same function. TryLock would be used exactly the same way.The TryLock is also faster because you have a single mutex. A channel is much more complex under the hood. That's why I want to have the option to go with something more primitive if I want to.Of course that's not clearer. That's still a TryLock. The whole pattern is the problem, not the implementation. Tell me what you want to use a TryLock to accomplish, and I'll either tell you whether I think there's a better way.Still cleaner than an actual TryLock. The pattern is still useful with certain problems. Not all problems can be solved by avoiding this pattern. If you could there would be no need for "default:".