TryUnlock() and IsLocked() functions

174 views
Skip to first unread message

John Souvestre

unread,
Jan 2, 2026, 9:42:24 PM (4 days ago) Jan 2
to golan...@googlegroups.com

Hi all.

 

I have a question about the mutexes provided by the sync library.  It is correct to say that using the 3 lines of code below, anywhere in Go code, won't have any effect, other than to introduce a slight, non-blocking delay while they execute?  In other words, the mutex is guaranteed to be the same before and after, and won’t hang your code, regardless of what else is going on, including in other GoRoutines.

 

       // Note: x is a sync.Mutex.

       if x.TryLock() {

              // x was unlocked but is now locked by us.

              x.Unlock()

       }

 

If so, then would these functions be non-blocking, correct, and safe to use?

 

// Note: x will always be unlocked upon return.

func TryUnlock(x *sync.Mutex) (success bool) {

       if x.TryLock() {

              // x was unlocked but is now locked by us.

              x.Unlock()

              return true

       }

       // x was unlocked to start with.

       return false

}

 

// Note: x will be the same (locked or unlocked) upon return.

func IsLocked(x *sync.Mutex) (locked bool) {

       if x.TryLock() {

              // x was unlocked but is now locked by us.

              x.Unlock()

              return false

       }

       // x was locked to start with.

       return true

}

 

I do understand that what I'm trying to achieve here is ok to do in only rare cases.  Indeed, I've only found a need for them and TryLock() on a few rare occasions.

 

Thanks,

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

Jason E. Aten

unread,
Jan 2, 2026, 10:01:13 PM (4 days ago) Jan 2
to golang-nuts
The main difficulty is here is akin to a use-after-check race. 

After you have done an Unlock, any other goroutine could have grabbed the lock, so returning false does not mean the lock is available. 

In fact it means nothing meaningful... unless you have otherwise guaranteed no lock contention, right? And if you've already guaranteed no contention by another mutex, why bother with this one?

If you expand on your larger goal, we might be able to suggest 
an approach that avoids TryLock. 

John Souvestre

unread,
Jan 3, 2026, 12:15:45 AM (4 days ago) Jan 3
to Jason E. Aten, golang-nuts

Hi Jason.

 

I see what you are saying.  Ok.  One idea: What if everyone else using this mutex used only the TryLock and TryUnlock functions and proceeding only if they returned “success”.  Then they would be honoring any pre-existing, or non-existing, lock.

 

This certainly isn’t what I originally had in mind, but it would be possible to make the changes.

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

--
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.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/b0d6a801-7799-4119-b5fa-24e77db4863en%40googlegroups.com.

Robert Engels

unread,
Jan 3, 2026, 12:31:42 AM (4 days ago) Jan 3
to John Souvestre, Jason E. Aten, golang-nuts

As Jason said it might be easier to go up a level and describe the problem you’re trying to solve. It is trivial to use a mutex to coordinate safely so it’s a bit unclear why you are complicating it with another layer. If you are trying to have things not block that is trivial too. 

Your basic problem is you are trying to ask is access available then get access - which is inherently racy. Breyer to just request access, and return try or false if it was granted - using TryLock and if true use defer to schedule the unlock. 

On Jan 2, 2026, at 11:15 PM, 'John Souvestre' via golang-nuts <golan...@googlegroups.com> wrote:



John Souvestre

unread,
Jan 3, 2026, 12:37:10 AM (4 days ago) Jan 3
to Jason E. Aten, golang-nuts

Hi again.  Ignore my 1st reply.  I posted that without thinking about it enough.

 

Let me ask this:  What do you say about my first question.  Are my assertions about sync’s TryLock() correct?  Is there any way that it can fail?  This question says that you do not proceed arbitrarily after the TryLock(), but only if it succeeded.  In that case, nobody can lock.  And nobody else is going to try to Unlock(), since they don’t hold a lock. 

 

It seems to me that this must be true, but if not them the rest of what I did certainly won’t work.

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

From: John Souvestre <Jo...@Souvestre.com>
Sent: 2026-01-02, Fri 23:15
To: 'Jason E. Aten' <j.e....@gmail.com>; 'golang-nuts' <golan...@googlegroups.com>
Subject: RE: [go-nuts] Re: TryUnlock() and IsLocked() functions

 

Hi Jason.

 

I see what you are saying.  Ok.  One idea: What if everyone else using this mutex used only the TryLock and TryUnlock functions and proceeding only if they returned “success”.  Then they would be honoring any pre-existing, or non-existing, lock.

 

This certainly isn’t what I originally had in mind, but it would be possible to make the changes.

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

From: golan...@googlegroups.com <golan...@googlegroups.com> On Behalf Of Jason E. Aten


Sent: 2026-01-02, Fri 21:01
To: golang-nuts <golan...@googlegroups.com>
Subject: [go-nuts] Re: TryUnlock() and IsLocked() functions

 

The main difficulty is here is akin to a use-after-check race. 

--

John Souvestre

unread,
Jan 3, 2026, 12:48:39 AM (4 days ago) Jan 3
to Robert Engels, Jason E. Aten, golang-nuts

I hear you.  I have developed work-arounds for the 2 situations I have in a program that I’m currently working on.  But TryLock() caught my attention and it seemed that using it as a basis I could build the other two functions I needed, based on it.

 

What situations?  One is trying to use defers when the code is flipping the lock on and off a few times.  It gets messy fast.  A single TryUnlock() eliminates that complexity.

 

Another situation is about trying to decide what can data can be displayed and what can’t, with the display routing running in its own GoRoutine, triggered by a timer tick.  The data being changed is protected by a mutex which can sometimes be locked while a user edits it.  I can’t have the display updates simply stop and wait.  The TryLock() lets me know if I can lock, and display, or if I should skip over that data.  Other solutions I used before v.18 add TryLock() worked, but added complexity.

 

I have an understanding of races.  But it seems to me that TryLock() solves this for itself.  And I’m building on top of it.

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

Jason E. Aten

unread,
Jan 3, 2026, 1:11:24 AM (4 days ago) Jan 3
to golang-nuts
Hi John,

you seem to be asking if the API documentation is correct... which is
a little... odd... so I'm trying to get a clearer picture of
the problem. Yes, if TryUnlock returns true, then you have the lock.

"TryLock tries to lock m and reports whether it succeeded."

Another situation is about trying to decide what can data can be displayed and what can’t, with the display routing running in its own GoRoutine, triggered by a timer tick.  The data being changed is protected by a mutex which can sometimes be locked while a user edits it.  I can’t have the display updates simply stop and wait.  The TryLock() lets me know if I can lock, and display, or if I should skip over that data. 

This seems reasonable to me.

One is trying to use defers when the code is flipping the lock on and off a few times.  It gets messy fast.  A single TryUnlock() eliminates that complexity.

It can be simpler just to manually (Lock and Unlock) in multiple places if one defer doesn't handle it for the whole function.

With regards to your specific question about your assertions:

[1] "// x was unlocked but is now locked by us."
my answer: [1] is true.

[2] "// Note: x will always be unlocked upon return."
my answer: [2] is false because another goroutine could now hold the lock by the time the function returns. The lock
could have been grabbed after the first goroutine unlocked but before it returned, and the other goroutine 
could be at the point labelled "// x was unlocked but is now locked by us."

[3] // Note: x will be the same (locked or unlocked) upon return.
my answer: [3] is true.

[4] // x was unlocked to start with.
my answer: [4] is false because the TryLock failed, so the x was Locked when [4] was reached.

[5] // Note: x will be the same (locked or unlocked) upon return.
my answer: [5] is true.

[6]  // x was locked to start with.
my answer: [6] is true.

IsLocked() is problematic name... WasLocked() would seem more accurate.

Are you seeing some kind of problem in your actual use that is prompting your queries? 

Robert Engels

unread,
Jan 3, 2026, 1:24:41 AM (4 days ago) Jan 3
to Jason E. Aten, golang-nuts

It sounds like you want a re-entrant lock (if you’re trying to do multi level locking). Which is ordinarily easy to do, except go doesn’t expose the go routine ID - so you need to use one of the published methods for that. (You can also use your own thread/context id). 

On Jan 3, 2026, at 12:12 AM, Jason E. Aten <j.e....@gmail.com> wrote:

Hi John,
--
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.

Robert Engels

unread,
Jan 3, 2026, 1:24:45 AM (4 days ago) Jan 3
to Jason E. Aten, golang-nuts
It sounds like you want a re-entrant lock (if you’re trying to do multi level locking). Which is ordinarily easy to do, except go doesn’t expose the go routine ID - so you need to use one of the published methods for that. (You can also use your own thread/context id). 

On Jan 3, 2026, at 12:12 AM, Jason E. Aten <j.e....@gmail.com> wrote:

Hi John,
--

John Souvestre

unread,
Jan 3, 2026, 1:47:33 AM (4 days ago) Jan 3
to Jason E. Aten, golang-nuts

Hi Jason.

 

Ok!  Great.  I felt that it should, but given the oddities about all the concurrency issues I wanted to make sure that I had it figured right.  😊

 

I do wonder why only TryLock() was added to the sync library.  Seems like adding its compliment, TryUnlock(), would have been a natural thing to do at the same time.  As I was describing to Robert a bit, I have a few cases where each would have been handy.  My workarounds at the time just added complexity for all the code using mutexes.  In contrast, the Try’s are simple to use.  You just have to remember to proceed only if they succeed, not fail.

 

Thanks for your thoughts. 

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

--

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.

John Souvestre

unread,
Jan 3, 2026, 2:13:07 AM (4 days ago) Jan 3
to Jason E. Aten, golang-nuts

Hi again.

 

I’m sorry.  I didn’t see the item-by-item notes when I first replied.  Let me address those now.

 

> It can be simpler just to manually (Lock and Unlock) in multiple places if one defer doesn't handle it for the whole function.

 

Certainly – unless you are worried that code might abort for other reasons, before it reaches the Unlock.  Having the deferred Unlock, which can run safely in any case, is nice.

 

> [2] is false because another goroutine could now hold the lock by the time ...

 

Granted.  I should have asserted that a return would guarantee an Unlock, which anyone else could then act on.

 > [4] is false because the TryLock failed, so the x was Locked when [4] was reached.

 

Ouch!  That was a typo.  I pasted the previous comment and didn't change enough of it.  :)

 

> IsLocked() is problematic name... WasLocked() would seem more accurate.

 

Good point.  Actually, I only use this to display the "current lock status" (part of some diagnostic info).  Perhaps name it: WasLockedaFewNanoSecondsAgo?  :)

 

> Are you seeing some kind of problem in your actual use that is prompting your queries?

 

No.  Actually, I'm using versions of each in my code right now.  The race detector hasn't complained and they seem to work.  But I didn't set up a test case to try them at high rates.

 

Thanks,

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

From: golan...@googlegroups.com <golan...@googlegroups.com> On Behalf Of Jason E. Aten


Sent: 2026-01-03, Sat 00:11
To: golang-nuts <golan...@googlegroups.com>

--

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.

Brian Candler

unread,
Jan 3, 2026, 6:59:33 AM (3 days ago) Jan 3
to golang-nuts
On Saturday, 3 January 2026 at 06:47:33 UTC John Souvestre wrote:

I do wonder why only TryLock() was added to the sync library.  Seems like adding its compliment, TryUnlock(), would have been a natural thing to do at the same time.


I cannot think of any case where TryUnlock() would be useful.  A Mutex can always be unlocked, if you have it locked, so there is no "Try". And if you don't know whether it's locked or not - or worse, you're trying to unlock a mutex that someone else already has locked - then your code is broken.

TryLock(), on the other hand, is useful:
1. You know you don't have the mutex
2. You want to lock it
3. But you don't want to wait if it's already locked; you'd rather do something else.

Bushnell, Thomas

unread,
Jan 5, 2026, 11:27:44 AM (yesterday) Jan 5
to John Souvestre, golan...@googlegroups.com

Your TryUnlock is incorrect. A separate goroutine could come in and lock the mutex after your call to TryLock but before you return. All the function guarantees is that at some point while it was executing the mutex was unlocked. The return value does not tell you anything useful about who unlocked it either, because it could have been locked and unlocked by someone else in between your call to Unlock and the return.

 

Your IsLocked is similarly incorrect. The invariant promised by the comment is certainly not true: a separate routine could change the state.

 

You are correct that your TryLock/Unlock sequence executes quickly without blocking, but it does not tell you anything interesting.

 

From: 'John Souvestre' via golang-nuts <golan...@googlegroups.com>
Sent: Friday, January 2, 2026 9:42 PM
To: golan...@googlegroups.com
Subject: [go-nuts] TryUnlock() and IsLocked() functions

 

This message was sent by an external party.

 

--

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

John Souvestre

unread,
Jan 5, 2026, 1:10:46 PM (yesterday) Jan 5
to Bushnell, Thomas, golan...@googlegroups.com

So you are saying that TryLock()’s return (succeeded or failed) might not be correct?

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

Jan Mercl

unread,
Jan 5, 2026, 1:13:01 PM (yesterday) Jan 5
to John Souvestre, Bushnell, Thomas, golan...@googlegroups.com
On Mon, Jan 5, 2026 at 7:10 PM 'John Souvestre' via golang-nuts
<golan...@googlegroups.com> wrote:

> So you are saying that TryLock()’s return (succeeded or failed) might not be correct?

See https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use

Brian Candler

unread,
Jan 5, 2026, 3:50:22 PM (yesterday) Jan 5
to golang-nuts
The standard library TryLock() gives correct answers.  Your code which returns true or false, only tells you whether the mutex happened to be locked or not locked at some instant in the past - which is generally not very useful.

Are you trying to measure how much lock contention is going on?  Then perhaps you could do something like this:

       if x.TryLock() {

              fmt.Println("No contention")

       } else {

              fmt.Println("Contention!")

              x.Lock()   // this blocks until the lock is obtained (of course it might already be free before this call)

       }  // either way, we now have the mutex locked

       defer x.Unlock()

       do_something()

       return

More sensibly, you might have two sync.atomic counters, and increment one or the other depending on which branch is taken.

John Souvestre

unread,
Jan 5, 2026, 7:42:39 PM (22 hours ago) Jan 5
to Jan Mercl, Bushnell, Thomas, golan...@googlegroups.com
Hi Jan.

> Time-of-check_to_time-of-use

Yes, that's the underlying problem and that's why I used TryLock() to start with. It resolves the problem atomically. I would imagine that it determines if the mutex is locked, then locks it regardless, by doing something like a "swap". But by itself that isn't useful. It also returns the prior state of the mutex. Thus, the user knows both the prior state and the current state. The only exception I can think of would be if a TryUnlock() was used after it - and perhaps that's why Go chose to just offer the one and not both.

The "if" inside TryUnlock() is acting on "time-of-check" information, and the mutex has been locked since then, and still is when TryUnlock()'s Unlock() executes.

I think that this might be less confusing if we can all agree that the description of TryLock() was enhanced a bit. Instead of:

"TryLock tries to lock m and reports whether it succeeded."

I take "succeed" to mean that TryLock() found the lock unset, and set it. The opposite, to "fail" means that TryLock() found the lock set, so didn't have to set it.

If so, I think this description would be clearer.

"TryLock reports success if it locks m, else failure due to m being already locked. Either way, m ends up being locked.

If we can agree to this much about TryLock()'s behavior, then consider that in my proposed TryUnlock(), action is only taken when my code verified that the mutex was unlocked to start with, and at that same time it was locked. So now I can safely execute an Unlock().

John

John Souvestre New Orleans LA, USA 504-454-0899

-----Original Message-----
From: Jan Mercl <0xj...@gmail.com>
Sent: 2026-01-05, Mon 12:12
To: John Souvestre <jo...@souvestre.com>

John Souvestre

unread,
Jan 5, 2026, 7:46:10 PM (22 hours ago) Jan 5
to Brian Candler, golang-nuts

Hi Brian.

 

  • The standard library TryLock() gives correct answers.  Your code which returns true or false, only tells you whether the mutex happened to be locked or not locked at some instant in the past - …

 

Not “some instant in the past” but the SAME instant that it is also locked.  TryLock() is doing a “test-and-set” atomic operation.  Else it would be useless.

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

From: 'Brian Candler' via golang-nuts <golan...@googlegroups.com>

Sent: 2026-01-05, Mon 14:50
To: golang-nuts <golan...@googlegroups.com>

--

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.

John Souvestre

unread,
Jan 5, 2026, 7:50:42 PM (22 hours ago) Jan 5
to Brian Candler, golang-nuts

P.S.

 

  • TryLock() is doing a “test-and-set” atomic operation.

 

I would expect that a regular mutex Lock() does the same thing, except that when evaluating the test result it panics instead of returning “false”.

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

From: John Souvestre <Jo...@Souvestre.com>
Sent: 2026-01-05, Mon 18:45
To: 'Brian Candler' <bcand...@googlemail.com>; 'golang-nuts' <golan...@googlegroups.com>
Subject: RE: [go-nuts] TryUnlock() and IsLocked() functions

 

Hi Brian.

 

  • The standard library TryLock() gives correct answers.  Your code which returns true or false, only tells you whether the mutex happened to be locked or not locked at some instant in the past - …

 

Not “some instant in the past” but the SAME instant that it is also locked.  TryLock() is doing a “test-and-set” atomic operation.  Else it would be useless.

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

From: 'Brian Candler' via golang-nuts <golan...@googlegroups.com>

Sent: 2026-01-05, Mon 14:50
To: golang-nuts <golan...@googlegroups.com>

--

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.

Def Ceb

unread,
Jan 5, 2026, 7:57:13 PM (22 hours ago) Jan 5
to golang-nuts
The only way to be sure that you have exclusive access with a sync.Mutex is to successfully acquire the lock, perform whatever actions you requested exclusive access for, and then release the lock.
You can not make any solid claim about the current state of the mutex without holding the lock. Once you've released it, you can only make these claims about a past state.

--
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.

John Souvestre

unread,
Jan 5, 2026, 8:17:34 PM (21 hours ago) Jan 5
to Def Ceb, golang-nuts

Hi Def.

 

  • The only way to be sure that you have exclusive access with a sync.Mutex is to successfully acquire the lock, perform whatever actions you requested exclusive access for, and then release the lock.

 

I agree.  TryLock() does this, I assume.  An TryUnlock() does this too – because the only action it performs is after TryLock() succeeds, hence locking the Mutex.

 

John

 

    John Souvestre    New Orleans LA, USA    504-454-0899

 

Brian Candler

unread,
5:15 AM (12 hours ago) 5:15 AM
to golang-nuts
On Tuesday, 6 January 2026 at 00:42:39 UTC John Souvestre wrote:
Yes, that's the underlying problem and that's why I used TryLock() to start with. It resolves the problem atomically. I would imagine that it determines if the mutex is locked, then locks it regardless, by doing something like a "swap".

Absolutely not. In fact, what happens is almost the complete opposite of this. Rather:

* If it finds the mutex is already locked, then it does nothing (and returns False).
* If it finds the mutex is unlocked, then it locks it (and returns True).

So, after the call to TryLock():
- if it returned True, then the mutex is now locked by you. You can proceed with whatever you wanted to do under protection of the mutex, and then unlock it afterwards.
- if it returned False, then nothing can be said, except that you definitely don't have the lock, and that the mutex was locked by someone else at that the instant in time when TryLock() was called. Since that point in time, it might have been released, or released and locked again, possibly multiple times. But you definitely don't have it now.

Normally you would call Lock() instead of TryLock().  This guarantees that once it returns, you will have the lock.  But it may block waiting for its turn, if someone else already has the lock, waiting for them to release it - and possibly for other people in front of view in the queue who were also waiting for the mutex.

The only reason to call TryLock() is if you prefer a failure to blocking. At which point, the question is, why were you trying to lock in the first place? As it says in the documentation at https://pkg.go.dev/sync#Mutex.TryLock

"Note that while correct uses of TryLock do exist, they are rare, and use of TryLock is often a sign of a deeper problem in a particular use of mutexes."

 
"TryLock tries to lock m and reports whether it succeeded."

I take "succeed" to mean that TryLock() found the lock unset, and set it. The opposite, to "fail" means that TryLock() found the lock set, so didn't have to set it.

You should say "couldn't set it" rather than "didn't have to set it".

If you find the lock set, that means someone else has it locked, and you must wait for your turn to get the lock.  That's what Mutex stands for: Mutual Exclusion (i.e. only one person can have the lock at any point in time).

If you want a kind of lock where you are allowed to lock it if you already have it locked, then that's a recursive mutex, which is something else.  Or maybe you want a semaphore.  What you actually want depends on exactly what you're trying to achieve.

And having said all that: in Go you often don't want to be using any of these primitives, when you have channels that can achieve the same thing much more elegantly and understandably.

This video is old but still as relevant as ever: "Rethinking Classical Concurrency Patterns" by Bryan C. Mills

There are explicit examples of where channels can cleanly replace other traditional concurrency mechanisms like mutexes and semaphores. Beware: you may have to stop and rewind and think a lot. I certainly did :-)

Brian Candler

unread,
5:19 AM (12 hours ago) 5:19 AM
to golang-nuts
On Tuesday, 6 January 2026 at 00:50:42 UTC John Souvestre wrote:

P.S.

 

  • TryLock() is doing a “test-and-set” atomic operation.

 

I would expect that a regular mutex Lock() does the same thing, except that when evaluating the test result it panics instead of returning “false”.


Then you expect wrongly.

What happens in this case is that Lock() will wait - indefinitely if necessary - until it can obtain the lock. Hence it always succeeds, eventually.

If nobody has the lock at the instant Lock() is called, then it will be immediately locked. Otherwise, a request goes into a queue of tasks waiting for the lock.  Each time the current lock holder unlocks it, then some task in the queue is granted the lock. Eventually your turn comes around, and you get the lock.

This is all standard CS stuff, nothing to do with Go.

Brian Candler

unread,
5:24 AM (12 hours ago) 5:24 AM
to golang-nuts
> Absolutely not. In fact, what happens is almost the complete opposite of this

Of course, the internal implementation detail of this is something like an atomic compare-and-swap operation in the hardware.

Say "0" = unlocked, "1" = locked"

Then replacing "0" with "1" is "success", and means you now have the lock.  Replacing "1" with "1" is "failure". You didn't get the lock, because somebody else already had it (at that point in time).

Brian Candler

unread,
5:39 AM (12 hours ago) 5:39 AM
to golang-nuts
On Tuesday, 6 January 2026 at 10:19:37 UTC Brian Candler wrote:
What happens in this case is that Lock() will wait - indefinitely if necessary - until it can obtain the lock. Hence it always succeeds, eventually.

I just realised something that might be causing confusion.  https://go.dev/play/p/rDseCVSFn-U

func main() {
var m sync.Mutex

m.Lock()  // succeeds
m.Lock()  // in principle, this hangs forever because m is already locked
}

In practice you will see a fatal error which looks like a panic. This is just that the Go runtime's deadlock detecter has determined that the program is stuck. It cannot possibly continue from this point because there is no way the mutex can become unlocked, to allow the second m.Lock() to succeed.

However, it can only detect simple deadlock cases.

Wendell Rios

unread,
12:46 PM (5 hours ago) 12:46 PM
to John Souvestre, Jan Mercl, Bushnell, Thomas, golan...@googlegroups.com
Hi John, I think you're maybe missing the point.
The problem is not with TryLock, but with a race in your
implementation of TryUnlock.
I'll try to make it clearer.

This is your implementation plus some comments:

func TryUnlock(x *sync.Mutex) (success bool) {
if x.TryLock() {
x.Unlock()
// <<<< Another goroutine calls x.Lock, or x.TryLock: x is now locked.
return true // <<< true here is no longer meaningful,
because it should mean that the lock is unlocked, but another
interspersed goroutine locked it.
}
return false
}

On Mon, Jan 5, 2026 at 9:41 PM 'John Souvestre' via golang-nuts
<golan...@googlegroups.com> wrote:
>
> Hi Jan.
>
> > Time-of-check_to_time-of-use
>
> Yes, that's the underlying problem and that's why I used TryLock() to start with. It resolves the problem atomically. I would imagine that it determines if the mutex is locked, then locks it regardless, by doing something like a "swap". But by itself that isn't useful. It also returns the prior state of the mutex. Thus, the user knows both the prior state and the current state. The only exception I can think of would be if a TryUnlock() was used after it - and perhaps that's why Go chose to just offer the one and not both.
>
> The "if" inside TryUnlock() is acting on "time-of-check" information, and the mutex has been locked since then, and still is when TryUnlock()'s Unlock() executes.
>
> I think that this might be less confusing if we can all agree that the description of TryLock() was enhanced a bit. Instead of:
>
> "TryLock tries to lock m and reports whether it succeeded."
>
> I take "succeed" to mean that TryLock() found the lock unset, and set it. The opposite, to "fail" means that TryLock() found the lock set, so didn't have to set it.
>
> If so, I think this description would be clearer.
>
> "TryLock reports success if it locks m, else failure due to m being already locked. Either way, m ends up being locked.
>
> If we can agree to this much about TryLock()'s behavior, then consider that in my proposed TryUnlock(), action is only taken when my code verified that the mutex was unlocked to start with, and at that same time it was locked. So now I can safely execute an Unlock().
>
> John
>
> John Souvestre New Orleans LA, USA 504-454-0899
>
> -----Original Message-----
> From: Jan Mercl <0xj...@gmail.com>
> Sent: 2026-01-05, Mon 12:12
> To: John Souvestre <jo...@souvestre.com>
> Cc: Bushnell, Thomas <Thomas....@deshaw.com>; golan...@googlegroups.com
> Subject: Re: [go-nuts] TryUnlock() and IsLocked() functions
>
> On Mon, Jan 5, 2026 at 7:10 PM 'John Souvestre' via golang-nuts
> <golan...@googlegroups.com> wrote:
>
> > So you are saying that TryLock()’s return (succeeded or failed) might not be correct?
>
> See https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use
>
> --
> 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.
> To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/000e01dc7ea5%2436c8dfb0%24a45a9f10%24%40Souvestre.com.

Brian Candler

unread,
2:04 PM (3 hours ago) 2:04 PM
to golang-nuts
The original function does not work in the way the author intended in the comment above the function:

// Note: x will always be unlocked upon return.

func TryUnlock(x *sync.Mutex) (success bool) {

       if x.TryLock() {

              // x was unlocked but is now locked by us.

              x.Unlock()

              return true

       }

       // x was unlocked to start with.

       return false

}


1. if the mutex is locked, TryLock() will fail. The function will return false, and the mutex could well still be locked (although it's also possible that some other goroutine unlocks it in the meantime)

2. if the mutex is not locked, TryLock() will succeed. The function will then unlock it, and the mutex could well be unlocked at function return (although it's also possible that some other goroutine locks it in the mean time).

Hence "x will always be unlocked upon return" is incorrect, as it's not changed by this function. And the true/false value tells you what the value was in the middle of execution of the function, but it could have changed by the time the function returns.

Reply all
Reply to author
Forward
0 new messages